1import Slider from '@react-native-community/slider'; 2import { Asset } from 'expo-asset'; 3import * as FileSystem from 'expo-file-system'; 4import * as GL from 'expo-gl'; 5import { GLView } from 'expo-gl'; 6import React from 'react'; 7import { Image, StyleSheet, Text, View } from 'react-native'; 8 9import exampleImage from '../../../assets/images/example3.jpg'; 10 11const vertShaderSource = `#version 300 es 12precision mediump float; 13 14in vec2 position; 15out vec2 textureCoord; 16 17void main() { 18 vec2 clipSpace = (1.0 - 2.0 * position) * vec2(-1.0, -1.0); 19 20 textureCoord = position; 21 gl_Position = vec4(clipSpace, 0.0, 1.0); 22}`; 23 24const fragShaderSource = `#version 300 es 25precision mediump float; 26 27uniform sampler2D inputImageTexture; 28uniform float contrast; 29 30in vec2 textureCoord; 31out vec4 fragColor; 32 33void main() { 34 vec4 textureColor = texture(inputImageTexture, textureCoord); 35 vec3 rgb = (textureColor.rgb - vec3(0.5)) * contrast + vec3(0.5); 36 37 fragColor = vec4(rgb, textureColor.a); 38}`; 39 40// location to contrast uniform 41let contrastLocation: WebGLUniformLocation | null; 42 43// create and prepare GL context 44const glPromise = GLView.createContextAsync().then(async gl => { 45 // Compile vertex and fragment shaders 46 const vertShader = gl.createShader(gl.VERTEX_SHADER)!; 47 gl.shaderSource(vertShader, vertShaderSource); 48 gl.compileShader(vertShader); 49 50 const fragShader = gl.createShader(gl.FRAGMENT_SHADER)!; 51 gl.shaderSource(fragShader, fragShaderSource); 52 gl.compileShader(fragShader); 53 54 // Link, use program, save and enable attributes 55 const program = gl.createProgram()!; 56 gl.attachShader(program, vertShader); 57 gl.attachShader(program, fragShader); 58 gl.linkProgram(program); 59 gl.validateProgram(program); 60 61 gl.useProgram(program); 62 63 contrastLocation = gl.getUniformLocation(program, 'contrast'); 64 65 // create framebuffer 66 const framebuffer = gl.createFramebuffer(); 67 gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); 68 69 // Create, bind, fill buffer 70 const buffer = gl.createBuffer(); 71 gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 72 73 const verts = new Float32Array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0]); 74 gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW); 75 76 // Bind 'position' attribute 77 const positionAttrib = gl.getAttribLocation(program, 'position'); 78 gl.enableVertexAttribArray(positionAttrib); 79 gl.vertexAttribPointer(positionAttrib, 2, gl.FLOAT, false, 0, 0); 80 81 // download an asset 82 const asset = Asset.fromModule(exampleImage); 83 await asset.downloadAsync(); 84 85 // set viewport 86 gl.viewport(0, 0, asset.width!, asset.height!); 87 88 // create output texture 89 const outputTexture = gl.createTexture(); 90 gl.activeTexture(gl.TEXTURE0); 91 gl.bindTexture(gl.TEXTURE_2D, outputTexture); 92 gl.texImage2D( 93 gl.TEXTURE_2D, 94 0, 95 gl.RGBA, 96 asset.width!, 97 asset.height!, 98 0, 99 gl.RGBA, 100 gl.UNSIGNED_BYTE, 101 null 102 ); 103 104 // create input texture 105 const inputTexture = gl.createTexture(); 106 gl.activeTexture(gl.TEXTURE1); 107 gl.bindTexture(gl.TEXTURE_2D, inputTexture); 108 109 // attach texture to framebuffer 110 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0); 111 112 // set texture parameters 113 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 114 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 115 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 116 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 117 118 // fill up input texture 119 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, asset as any); 120 gl.uniform1i(gl.getUniformLocation(program, 'inputImageTexture'), 1); 121 122 gl.clearColor(0, 0, 0, 1); 123 // tslint:disable-next-line: no-bitwise 124 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 125 126 return gl; 127}); 128 129interface State { 130 contrast: number; 131 snapshot?: GL.GLSnapshot; 132} 133 134// See: https://github.com/expo/expo/pull/10229#discussion_r490961694 135// eslint-disable-next-line @typescript-eslint/ban-types 136export default class GLHeadlessRenderingScreen extends React.PureComponent<{}, State> { 137 static title = 'Headless rendering'; 138 139 isDrawing = false; 140 141 readonly state: State = { 142 contrast: 1.0, 143 }; 144 145 componentDidMount() { 146 this.draw(); 147 } 148 149 async draw() { 150 if (this.isDrawing) { 151 // if another draw call is already running, we need to skip this one 152 return; 153 } 154 155 this.isDrawing = true; 156 157 const gl = await glPromise; 158 159 gl.uniform1f(contrastLocation, this.state.contrast); 160 gl.drawArrays(gl.TRIANGLES, 0, 6); 161 gl.endFrameEXP(); 162 163 // we need to use flip option because framebuffer contents are flipped vertically 164 const snapshot = await GLView.takeSnapshotAsync(gl, { 165 flip: true, 166 }); 167 168 // delete previous snapshot 169 if (this.state.snapshot) { 170 FileSystem.deleteAsync(this.state.snapshot.uri as string, { idempotent: true }); 171 } 172 173 this.setState({ snapshot }); 174 this.isDrawing = false; 175 } 176 177 onContrastChange = (contrast: number) => { 178 this.setState({ contrast }, () => { 179 this.draw(); 180 }); 181 }; 182 183 render() { 184 const { contrast, snapshot } = this.state; 185 186 return ( 187 <View style={styles.container}> 188 <Text> 189 This example shows how to use headless rendering context with Expo.GLView. Instead of the 190 GLView, it renders snapshots taken from headless context that sits on top of the app and 191 can be reused in multiple components without redundant shader recompilations. 192 </Text> 193 <View style={styles.flex}> 194 {snapshot && ( 195 <Image 196 style={styles.flex} 197 fadeDuration={0} 198 source={{ uri: snapshot.uri as string }} 199 resizeMode="contain" 200 /> 201 )} 202 </View> 203 <Text style={styles.sliderHeader}>{`Contrast: ${parseInt( 204 String(contrast * 100), 205 10 206 )}%`}</Text> 207 <Slider 208 value={contrast} 209 step={0.01} 210 minimumValue={0} 211 maximumValue={2} 212 onValueChange={this.onContrastChange} 213 /> 214 </View> 215 ); 216 } 217} 218 219const styles = StyleSheet.create({ 220 container: { 221 flex: 1, 222 padding: 10, 223 }, 224 flex: { 225 flex: 1, 226 width: '100%', 227 }, 228 sliderHeader: { 229 paddingHorizontal: 10, 230 }, 231}); 232