1import { Asset } from 'expo-asset';
2import ExpoCheckbox from 'expo-checkbox';
3import { ExpoWebGLRenderingContext, GLView } from 'expo-gl';
4import React, { useEffect, useState } from 'react';
5import { Text, StyleSheet, View, TouchableHighlight } from 'react-native';
6import { runOnUI } from 'react-native-reanimated';
7
8async function onContextCreate(gl: ExpoWebGLRenderingContext) {
9  const vert = gl.createShader(gl.VERTEX_SHADER)!;
10  gl.shaderSource(
11    vert,
12    `
13  precision highp float;
14  attribute vec2 position;
15  varying vec2 uv;
16  void main () {
17    uv = position;
18    gl_Position = vec4(1.0 - 2.0 * position, 0, 1);
19  }`
20  );
21  gl.compileShader(vert);
22  const frag = gl.createShader(gl.FRAGMENT_SHADER)!;
23  gl.shaderSource(
24    frag,
25    `
26  precision highp float;
27  uniform sampler2D texture;
28  varying vec2 uv;
29  void main () {
30    gl_FragColor = texture2D(texture, vec2(uv.x, uv.y));
31  }`
32  );
33  gl.compileShader(frag);
34
35  const program = gl.createProgram()!;
36  gl.attachShader(program, vert);
37  gl.attachShader(program, frag);
38  gl.linkProgram(program);
39  gl.useProgram(program);
40
41  const buffer = gl.createBuffer();
42  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
43  const verts = new Float32Array([-2, 0, 0, -2, 2, 2]);
44  gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW);
45  const positionAttrib = gl.getAttribLocation(program, 'position');
46  gl.enableVertexAttribArray(positionAttrib);
47  gl.vertexAttribPointer(positionAttrib, 2, gl.FLOAT, false, 0, 0);
48
49  const asset = Asset.fromModule(require('../../../assets/images/nikki.png'));
50  await asset.downloadAsync();
51  const texture = gl.createTexture();
52  gl.activeTexture(gl.TEXTURE0);
53  gl.bindTexture(gl.TEXTURE_2D, texture);
54  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
55  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
56  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, asset as any);
57  gl.uniform1i(gl.getUniformLocation(program, 'texture'), 0);
58  gl.clearColor(0, 0, 1, 1);
59  // tslint:disable-next-line: no-bitwise
60  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
61  gl.drawArrays(gl.TRIANGLES, 0, verts.length / 2);
62  gl.endFrameEXP();
63}
64
65function BusyJSThreadSelector() {
66  const [fakeBusyJSThread, setFakeBusyJSThread] = useState(false);
67  useEffect(() => {
68    if (!fakeBusyJSThread) {
69      return;
70    }
71    const interval = setInterval(() => {
72      let test_value = 0;
73      const start = Date.now();
74      while (Date.now() - start < 990) {
75        test_value += Math.random();
76      }
77      console.log(`js - ${test_value}`);
78    }, 1000);
79    return () => clearInterval(interval);
80  }, [fakeBusyJSThread]);
81
82  return (
83    <View style={styles.checkboxRow}>
84      <ExpoCheckbox
85        onValueChange={() => setFakeBusyJSThread(!fakeBusyJSThread)}
86        value={fakeBusyJSThread}
87      />
88      <Text style={styles.text}>fake work on js thread</Text>
89    </View>
90  );
91}
92
93function BusyWorkletThreadSelector() {
94  const [fakeBusyWorkletThread, setFakeBusyWorkletThread] = useState(true);
95  useEffect(() => {
96    if (!fakeBusyWorkletThread) {
97      return;
98    }
99    const interval = setInterval(() => {
100      runOnUI(() => {
101        'worklet';
102        let test_value = 0;
103        const start = Date.now();
104        while (Date.now() - start < 990) {
105          test_value += Math.random();
106        }
107        console.log(`worklet - ${test_value}`);
108      })();
109    }, 1000);
110    return () => clearInterval(interval);
111  }, [fakeBusyWorkletThread]);
112
113  return (
114    <View style={styles.checkboxRow}>
115      <ExpoCheckbox
116        onValueChange={() => setFakeBusyWorkletThread(!fakeBusyWorkletThread)}
117        value={fakeBusyWorkletThread}
118      />
119      <Text style={styles.text}>fake work on worklet thread</Text>
120    </View>
121  );
122}
123
124export default function GLViewOnBusyThread() {
125  const [show, setShow] = useState(true);
126  return (
127    <View style={styles.flex}>
128      <Text style={styles.text}>
129        This screen is expected to lag. It's faking work on JS and worklet threads. Toggle GLView
130        few times to make sure it does no crash.
131      </Text>
132      <BusyWorkletThreadSelector />
133      <BusyJSThreadSelector />
134
135      {show ? (
136        <GLView
137          style={styles.flex}
138          onContextCreate={onContextCreate}
139          enableExperimentalWorkletSupport
140        />
141      ) : (
142        <View style={styles.placeholder}>
143          <Text>no gl view</Text>
144        </View>
145      )}
146      <TouchableHighlight
147        underlayColor="#0176d3"
148        style={styles.button}
149        onPress={() => setShow(!show)}>
150        <Text style={styles.buttonText}>TOGGLE</Text>
151      </TouchableHighlight>
152    </View>
153  );
154}
155
156GLViewOnBusyThread.title = 'Creating GLView when a thread is busy';
157
158const styles = StyleSheet.create({
159  flex: {
160    flex: 1,
161  },
162  checkboxRow: {
163    flexDirection: 'row',
164    alignItems: 'center',
165    marginLeft: 10,
166  },
167  placeholder: {
168    flex: 1,
169    justifyContent: 'center',
170    alignItems: 'center',
171  },
172  text: {
173    padding: 10,
174    fontSize: 16,
175  },
176  buttonText: {
177    fontSize: 22,
178  },
179  button: {
180    height: 100,
181    alignItems: 'center',
182    justifyContent: 'center',
183    backgroundColor: '#2196f3',
184    marginTop: 10,
185  },
186});
187