From 1a08dd2bd6839f0be4b12dc5317ab0b00ca23ae1 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Tue, 16 Mar 2021 22:03:10 -0400 Subject: Proper update loop for a WebAssembly scene running --- frontend/_wasm/build.sh | 0 frontend/_wasm/intro.html | 6 +-- frontend/_wasm/mathlib.h | 17 ++++++- frontend/_wasm/output.js | 116 ++++++++++++++++++++++++++++++------------ frontend/_wasm/output.wasm | Bin 26645 -> 29351 bytes frontend/_wasm/wasm.cpp | 124 +++++++++++++++++++++++++++++++++++---------- frontend/_wasm/wasm.js | 11 +++- frontend/index.css | 60 ++++++++++------------ 8 files changed, 240 insertions(+), 94 deletions(-) mode change 100644 => 100755 frontend/_wasm/build.sh mode change 100644 => 100755 frontend/_wasm/output.wasm (limited to 'frontend') diff --git a/frontend/_wasm/build.sh b/frontend/_wasm/build.sh old mode 100644 new mode 100755 diff --git a/frontend/_wasm/intro.html b/frontend/_wasm/intro.html index 853d7ac..9b0d6d5 100644 --- a/frontend/_wasm/intro.html +++ b/frontend/_wasm/intro.html @@ -42,10 +42,10 @@
- - @@ -53,4 +53,4 @@ - \ No newline at end of file + diff --git a/frontend/_wasm/mathlib.h b/frontend/_wasm/mathlib.h index bc84b72..93ddbbd 100644 --- a/frontend/_wasm/mathlib.h +++ b/frontend/_wasm/mathlib.h @@ -86,6 +86,13 @@ struct Mat4x4 { return result; } + Mat4x4 translateByVec2(Vector2 v) { + Mat4x4 result = copy(); + result.m[12] += v.x; + result.m[13] += v.y; + return result; + } + Mat4x4 rotate2D(float angle) { Mat4x4 result = copy(); result.m[0] = cos(angle); @@ -112,4 +119,12 @@ struct Mat4x4 { result.m[13] = -(top + bottom) / (top - bottom); return result; } -}; \ No newline at end of file + + void print() { + printf("[ "); + for (int idx = 0; idx < 16; idx++) { + printf("%d, ", idx); + } + printf(" ]\n"); + } +}; diff --git a/frontend/_wasm/output.js b/frontend/_wasm/output.js index 0a0a401..ee8462a 100644 --- a/frontend/_wasm/output.js +++ b/frontend/_wasm/output.js @@ -252,9 +252,7 @@ moduleOverrides = null; // to the proper local x. This has two benefits: first, we only emit it if it is // expected to arrive, and second, by using a local everywhere else that can be // minified. - -if (Module['arguments']) arguments_ = Module['arguments']; -if (!Object.getOwnPropertyDescriptor(Module, 'arguments')) { +if (Module['arguments']) arguments_ = Module['arguments'];if (!Object.getOwnPropertyDescriptor(Module, 'arguments')) { Object.defineProperty(Module, 'arguments', { configurable: true, get: function() { @@ -262,9 +260,7 @@ if (!Object.getOwnPropertyDescriptor(Module, 'arguments')) { } }); } - -if (Module['thisProgram']) thisProgram = Module['thisProgram']; -if (!Object.getOwnPropertyDescriptor(Module, 'thisProgram')) { +if (Module['thisProgram']) thisProgram = Module['thisProgram'];if (!Object.getOwnPropertyDescriptor(Module, 'thisProgram')) { Object.defineProperty(Module, 'thisProgram', { configurable: true, get: function() { @@ -272,9 +268,7 @@ if (!Object.getOwnPropertyDescriptor(Module, 'thisProgram')) { } }); } - -if (Module['quit']) quit_ = Module['quit']; -if (!Object.getOwnPropertyDescriptor(Module, 'quit')) { +if (Module['quit']) quit_ = Module['quit'];if (!Object.getOwnPropertyDescriptor(Module, 'quit')) { Object.defineProperty(Module, 'quit', { configurable: true, get: function() { @@ -294,7 +288,6 @@ assert(typeof Module['readAsync'] === 'undefined', 'Module.readAsync option was assert(typeof Module['readBinary'] === 'undefined', 'Module.readBinary option was removed (modify readBinary in JS)'); assert(typeof Module['setWindowTitle'] === 'undefined', 'Module.setWindowTitle option was removed (modify setWindowTitle in JS)'); assert(typeof Module['TOTAL_MEMORY'] === 'undefined', 'Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY'); - if (!Object.getOwnPropertyDescriptor(Module, 'read')) { Object.defineProperty(Module, 'read', { configurable: true, @@ -303,7 +296,6 @@ if (!Object.getOwnPropertyDescriptor(Module, 'read')) { } }); } - if (!Object.getOwnPropertyDescriptor(Module, 'readAsync')) { Object.defineProperty(Module, 'readAsync', { configurable: true, @@ -312,7 +304,6 @@ if (!Object.getOwnPropertyDescriptor(Module, 'readAsync')) { } }); } - if (!Object.getOwnPropertyDescriptor(Module, 'readBinary')) { Object.defineProperty(Module, 'readBinary', { configurable: true, @@ -321,7 +312,6 @@ if (!Object.getOwnPropertyDescriptor(Module, 'readBinary')) { } }); } - if (!Object.getOwnPropertyDescriptor(Module, 'setWindowTitle')) { Object.defineProperty(Module, 'setWindowTitle', { configurable: true, @@ -572,9 +562,7 @@ function getCompilerSetting(name) { // An online HTML version (which may be of a different version of Emscripten) // is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html -var wasmBinary; -if (Module['wasmBinary']) wasmBinary = Module['wasmBinary']; -if (!Object.getOwnPropertyDescriptor(Module, 'wasmBinary')) { +var wasmBinary;if (Module['wasmBinary']) wasmBinary = Module['wasmBinary'];if (!Object.getOwnPropertyDescriptor(Module, 'wasmBinary')) { Object.defineProperty(Module, 'wasmBinary', { configurable: true, get: function() { @@ -582,8 +570,7 @@ if (!Object.getOwnPropertyDescriptor(Module, 'wasmBinary')) { } }); } -var noExitRuntime = Module['noExitRuntime'] || true; -if (!Object.getOwnPropertyDescriptor(Module, 'noExitRuntime')) { +var noExitRuntime;if (Module['noExitRuntime']) noExitRuntime = Module['noExitRuntime'];if (!Object.getOwnPropertyDescriptor(Module, 'noExitRuntime')) { Object.defineProperty(Module, 'noExitRuntime', { configurable: true, get: function() { @@ -1195,8 +1182,7 @@ function updateGlobalBufferAndViews(buf) { var TOTAL_STACK = 5242880; if (Module['TOTAL_STACK']) assert(TOTAL_STACK === Module['TOTAL_STACK'], 'the stack size can no longer be determined at runtime') -var INITIAL_MEMORY = Module['INITIAL_MEMORY'] || 16777216; -if (!Object.getOwnPropertyDescriptor(Module, 'INITIAL_MEMORY')) { +var INITIAL_MEMORY = Module['INITIAL_MEMORY'] || 16777216;if (!Object.getOwnPropertyDescriptor(Module, 'INITIAL_MEMORY')) { Object.defineProperty(Module, 'INITIAL_MEMORY', { configurable: true, get: function() { @@ -1774,6 +1760,15 @@ var ASM_CONSTS = { HEAPU8.copyWithin(dest, src, src + num); } + function _emscripten_request_animation_frame_loop(cb, userData) { + function tick(timeStamp) { + if (wasmTable.get(cb)(timeStamp, userData)) { + requestAnimationFrame(tick); + } + } + return requestAnimationFrame(tick); + } + function _emscripten_get_heap_size() { return HEAPU8.length; } @@ -1791,31 +1786,32 @@ var ASM_CONSTS = { // anyhow) } function _emscripten_resize_heap(requestedSize) { + requestedSize = requestedSize >>> 0; var oldSize = _emscripten_get_heap_size(); // With pthreads, races can happen (another thread might increase the size in between), so return a failure, and let the caller retry. assert(requestedSize > oldSize); // Memory resize rules: - // 1. Always increase heap size to at least the requested size, rounded up to next page multiple. - // 2a. If MEMORY_GROWTH_LINEAR_STEP == -1, excessively resize the heap geometrically: increase the heap size according to + // 1. When resizing, always produce a resized heap that is at least 16MB (to avoid tiny heap sizes receiving lots of repeated resizes at startup) + // 2. Always increase heap size to at least the requested size, rounded up to next page multiple. + // 3a. If MEMORY_GROWTH_LINEAR_STEP == -1, excessively resize the heap geometrically: increase the heap size according to // MEMORY_GROWTH_GEOMETRIC_STEP factor (default +20%), // At most overreserve by MEMORY_GROWTH_GEOMETRIC_CAP bytes (default 96MB). - // 2b. If MEMORY_GROWTH_LINEAR_STEP != -1, excessively resize the heap linearly: increase the heap size by at least MEMORY_GROWTH_LINEAR_STEP bytes. - // 3. Max size for the heap is capped at 2048MB-WASM_PAGE_SIZE, or by MAXIMUM_MEMORY, or by ASAN limit, depending on which is smallest - // 4. If we were unable to allocate as much memory, it may be due to over-eager decision to excessively reserve due to (3) above. + // 3b. If MEMORY_GROWTH_LINEAR_STEP != -1, excessively resize the heap linearly: increase the heap size by at least MEMORY_GROWTH_LINEAR_STEP bytes. + // 4. Max size for the heap is capped at 2048MB-WASM_PAGE_SIZE, or by MAXIMUM_MEMORY, or by ASAN limit, depending on which is smallest + // 5. If we were unable to allocate as much memory, it may be due to over-eager decision to excessively reserve due to (3) above. // Hence if an allocation fails, cut down on the amount of excess growth, in an attempt to succeed to perform a smaller allocation. // A limit was set for how much we can grow. We should not exceed that // (the wasm binary specifies it, so if we tried, we'd fail anyhow). - // In CAN_ADDRESS_2GB mode, stay one Wasm page short of 4GB: while e.g. Chrome is able to allocate full 4GB Wasm memories, the size will wrap - // back to 0 bytes in Wasm side for any code that deals with heap sizes, which would require special casing all heap size related code to treat - // 0 specially. var maxHeapSize = 2147483648; if (requestedSize > maxHeapSize) { err('Cannot enlarge memory, asked to go up to ' + requestedSize + ' bytes, but the limit is ' + maxHeapSize + ' bytes!'); return false; } + var minHeapSize = 16777216; + // Loop through potential heap size increases. If we attempt a too eager reservation that fails, cut down on the // attempted size and reserve a smaller bump instead. (max 3 times, chosen somewhat arbitrarily) for(var cutDown = 1; cutDown <= 4; cutDown *= 2) { @@ -1823,7 +1819,7 @@ var ASM_CONSTS = { // but limit overreserving (default to capping at +96MB overgrowth at most) overGrownHeapSize = Math.min(overGrownHeapSize, requestedSize + 100663296 ); - var newSize = Math.min(maxHeapSize, alignUp(Math.max(requestedSize, overGrownHeapSize), 65536)); + var newSize = Math.min(maxHeapSize, alignUp(Math.max(minHeapSize, requestedSize, overGrownHeapSize), 65536)); var replacement = emscripten_realloc_buffer(newSize); if (replacement) { @@ -1963,6 +1959,62 @@ var ASM_CONSTS = { return 0; } + function getBoundingClientRect(e) { + return specialHTMLTargets.indexOf(e) < 0 ? e.getBoundingClientRect() : {'left':0,'top':0}; + } + function fillMouseEventData(eventStruct, e, target) { + assert(eventStruct % 4 == 0); + var idx = eventStruct >> 2; + HEAP32[idx + 0] = e.screenX; + HEAP32[idx + 1] = e.screenY; + HEAP32[idx + 2] = e.clientX; + HEAP32[idx + 3] = e.clientY; + HEAP32[idx + 4] = e.ctrlKey; + HEAP32[idx + 5] = e.shiftKey; + HEAP32[idx + 6] = e.altKey; + HEAP32[idx + 7] = e.metaKey; + HEAP16[idx*2 + 16] = e.button; + HEAP16[idx*2 + 17] = e.buttons; + + HEAP32[idx + 9] = e["movementX"] + ; + + HEAP32[idx + 10] = e["movementY"] + ; + + var rect = getBoundingClientRect(target); + HEAP32[idx + 11] = e.clientX - rect.left; + HEAP32[idx + 12] = e.clientY - rect.top; + + } + function registerMouseEventCallback(target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) { + if (!JSEvents.mouseEvent) JSEvents.mouseEvent = _malloc( 64 ); + target = findEventTarget(target); + + var mouseEventHandlerFunc = function(ev) { + var e = ev || event; + + // TODO: Make this access thread safe, or this could update live while app is reading it. + fillMouseEventData(JSEvents.mouseEvent, e, target); + + if (wasmTable.get(callbackfunc)(eventTypeId, JSEvents.mouseEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target: target, + allowsDeferredCalls: eventTypeString != 'mousemove' && eventTypeString != 'mouseenter' && eventTypeString != 'mouseleave', // Mouse move events do not allow fullscreen/pointer lock requests to be handled in them! + eventTypeString: eventTypeString, + callbackfunc: callbackfunc, + handlerFunc: mouseEventHandlerFunc, + useCapture: useCapture + }; + JSEvents.registerOrRemoveHandler(eventHandler); + } + function _emscripten_set_click_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + registerMouseEventCallback(target, userData, useCapture, callbackfunc, 4, "click", targetThread); + return 0; + } + function __webgl_enable_ANGLE_instanced_arrays(ctx) { // Extension available in WebGL 1 from Firefox 26 and Google Chrome 30 onwards. Core feature in WebGL 2. var ext = ctx.getExtension('ANGLE_instanced_arrays'); @@ -2762,8 +2814,10 @@ function intArrayToString(array) { var asmLibraryArg = { "abort": _abort, "emscripten_memcpy_big": _emscripten_memcpy_big, + "emscripten_request_animation_frame_loop": _emscripten_request_animation_frame_loop, "emscripten_resize_heap": _emscripten_resize_heap, "emscripten_set_canvas_element_size": _emscripten_set_canvas_element_size, + "emscripten_set_click_callback_on_thread": _emscripten_set_click_callback_on_thread, "emscripten_webgl_create_context": _emscripten_webgl_create_context, "emscripten_webgl_init_context_attributes": _emscripten_webgl_init_context_attributes, "emscripten_webgl_make_context_current": _emscripten_webgl_make_context_current, @@ -2907,8 +2961,6 @@ if (!Object.getOwnPropertyDescriptor(Module, "ENV")) Module["ENV"] = function() if (!Object.getOwnPropertyDescriptor(Module, "ERRNO_CODES")) Module["ERRNO_CODES"] = function() { abort("'ERRNO_CODES' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "ERRNO_MESSAGES")) Module["ERRNO_MESSAGES"] = function() { abort("'ERRNO_MESSAGES' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "setErrNo")) Module["setErrNo"] = function() { abort("'setErrNo' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; -if (!Object.getOwnPropertyDescriptor(Module, "readSockaddr")) Module["readSockaddr"] = function() { abort("'readSockaddr' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; -if (!Object.getOwnPropertyDescriptor(Module, "writeSockaddr")) Module["writeSockaddr"] = function() { abort("'writeSockaddr' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "DNS")) Module["DNS"] = function() { abort("'DNS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "getHostByName")) Module["getHostByName"] = function() { abort("'getHostByName' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "GAI_ERRNO_MESSAGES")) Module["GAI_ERRNO_MESSAGES"] = function() { abort("'GAI_ERRNO_MESSAGES' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; @@ -3272,6 +3324,8 @@ var shouldRunNow = true; if (Module['noInitialRun']) shouldRunNow = false; +noExitRuntime = true; + run(); diff --git a/frontend/_wasm/output.wasm b/frontend/_wasm/output.wasm old mode 100644 new mode 100755 index bde2a03..2ec3de9 Binary files a/frontend/_wasm/output.wasm and b/frontend/_wasm/output.wasm differ diff --git a/frontend/_wasm/wasm.cpp b/frontend/_wasm/wasm.cpp index 91d440f..8673c80 100644 --- a/frontend/_wasm/wasm.cpp +++ b/frontend/_wasm/wasm.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,7 @@ struct OrthographicVertex { // Create a triangle struct TriangleObject { OrthographicVertex vertices[3]; + Vector2 velocity; GLuint mVao; GLuint mVbo; Mat4x4 model; @@ -88,6 +90,28 @@ struct TriangleObject { }; +struct Scene { + Mat4x4 projection; + TriangleObject triangleObject; + OrthographicProgramData programData; + bool isTerminated = false; +}; + +long long timeInMilliseconds(void) { + struct timeval tv; + + gettimeofday(&tv,NULL); + return (((long long)tv.tv_sec)*1000)+(tv.tv_usec/1000); +} + +long elapsedTime = 0; +long lastTime = 0; +int numFrames = 0; + +EM_BOOL update(double time, void* userData); +EM_BOOL runScene(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); +EM_BOOL terminateScene(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); + int main() { printf("Initializing the canvas...\n"); emscripten_set_canvas_element_size( "#wasm_canvas", 640, 480 ); @@ -111,35 +135,83 @@ int main() { } emscripten_webgl_make_context_current(context); printf("Canvas ready.\n"); - - printf("Compiling shaders...\n"); - GLuint shader = loadShader(orthographicVertex, orthographicFragment); - useShader(shader); - OrthographicProgramData programData; - programData.shader = shader; - programData.attributes.position = getShaderAttribute(shader, "position"); - programData.attributes.color = getShaderAttribute(shader, "color"); - programData.uniformVariables.projection = getShaderUniform(shader, "projection"); - programData.uniformVariables.model = getShaderUniform(shader, "model"); - printf("Compiling ready.\n"); - - TriangleObject triangle; - triangle.vertices[0] = { Vector2 { 50, 50 }, Vector4 { 1.f, 0.f, 0.f, 1.f }}; - triangle.vertices[1] = { Vector2 { 150, 300 }, Vector4 { 0.f, 1.f, 0.f, 1.f }}; - triangle.vertices[2] = { Vector2 { 250, 50 }, Vector4 { 0.f, 0.f, 1.f, 1.f }}; - triangle.initialize(&programData); - - Mat4x4 projection = Mat4x4().getOrthographicMatrix(0, 480, 0, 640); - - glEnable(GL_DEPTH_TEST); + + emscripten_set_click_callback("#wasm_canvas_play_button", NULL, false, runScene); + emscripten_set_click_callback("#wasm_canvas_stop_button", NULL, false, terminateScene); + return EXIT_SUCCESS; +} + +Scene scene; + +EM_BOOL runScene(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) { + printf("Compiling shaders...\n"); + scene.programData.shader = loadShader(orthographicVertex, orthographicFragment); + printf("Shaders compiled.\n"); + + + printf("Initializing scene...\n"); + useShader(scene.programData.shader); + scene.programData.attributes.position = getShaderAttribute(scene.programData.shader, "position"); + scene.programData.attributes.color = getShaderAttribute(scene.programData.shader, "color"); + scene.programData.uniformVariables.projection = getShaderUniform(scene.programData.shader, "projection"); + scene.programData.uniformVariables.model = getShaderUniform(scene.programData.shader, "model"); + + scene.triangleObject.vertices[0] = { Vector2 { 220, 365 }, Vector4 { 1.f, 0.f, 0.f, 1.f }}; + scene.triangleObject.vertices[1] = { Vector2 { 320, 480 }, Vector4 { 0.f, 1.f, 0.f, 1.f }}; + scene.triangleObject.vertices[2] = { Vector2 { 420, 365 }, Vector4 { 0.f, 0.f, 1.f, 1.f }}; + scene.triangleObject.initialize(&scene.programData); + + scene.projection = Mat4x4().getOrthographicMatrix(0, 640, 0, 480); + scene.isTerminated = false; + printf("Scene initialized.\n"); + + emscripten_request_animation_frame_loop(update, &scene); + return true; +} + +EM_BOOL update(double time, void* userData) { + Scene* scene = (Scene*)userData; + if (scene->isTerminated) { + return false; + } + + if (lastTime == 0) { + lastTime = time; + return true; + } + + long deltaTime = time - lastTime; + lastTime = time; + elapsedTime += deltaTime; + numFrames++; + + if (elapsedTime >= 1000.0) { + printf("Frames Per Second: %d\n", numFrames); + numFrames = 0; + elapsedTime = 0.0; + } + + float deltaTimeSeconds = static_cast(deltaTime) / 1000.f; + + // Update + scene->triangleObject.velocity = scene->triangleObject.velocity + Vector2 { 0, static_cast(-9.8 * deltaTimeSeconds) }; + scene->triangleObject.model = scene->triangleObject.model.translateByVec2(scene->triangleObject.velocity * deltaTimeSeconds); + + // Render + glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glDepthMask(GL_TRUE); glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClearDepth(1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - useShader(shader); - setShaderMat4(programData.uniformVariables.projection, projection); - triangle.render(&programData); + useShader(scene->programData.shader); + setShaderMat4(scene->programData.uniformVariables.projection, scene->projection); + scene->triangleObject.render(&scene->programData); - return EXIT_SUCCESS; -} \ No newline at end of file + return true; +} + +EM_BOOL terminateScene(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) { + scene.isTerminated = true; + return true; +} diff --git a/frontend/_wasm/wasm.js b/frontend/_wasm/wasm.js index 6a3bfae..37d5faf 100644 --- a/frontend/_wasm/wasm.js +++ b/frontend/_wasm/wasm.js @@ -3,8 +3,17 @@ (function() { function main() { + $('#wasm_canvas_play_button').on('click', function() { + $(this).hide(); + $("#wasm_canvas_stop_button").show(); + }); + + $('#wasm_canvas_stop_button').on('click', function() { + $(this).hide(); + $("#wasm_canvas_play_button").show(); + }); } $(document).ready(main); -})() \ No newline at end of file +})() diff --git a/frontend/index.css b/frontend/index.css index 010d543..9f23e96 100644 --- a/frontend/index.css +++ b/frontend/index.css @@ -1,31 +1,36 @@ +html { + width: 100vw; + background: #2f3032; +} + body { + background: #2f3032; + min-height: calc(100vh - 64px); + background: -webkit-radial-gradient(top, #383A56, #303133); + background: -moz-radial-gradient(top, #383A56, #303133); + background: radial-gradient(to bottom, #383A56, #303133); font-size: 16px; font-family: "Helvetica", sans-serif; - line-height: 1.5rem; - width: 100%; - height: 100vh; - margin: 0rem; - padding: 0rem; + line-height: 1.75rem; + width: 960px; + margin: auto; overflow-x: auto; overflow-y: auto; - background-color: #2f3032; color: white; + margin-top: 32px; + margin-bottom: 32px; + border-radius: 8px; + border: 1px solid #383A56; + padding: 32px; } header { + border-bottom: 2px solid #2f3032; padding-top: 1rem; padding-bottom: 1rem; font-size: 20px; - margin: auto; - width: 1260px; - max-width: 1260px; text-align: center; border-radius: 0px 0px 3px 3px; - - background: #383A56; - background: -webkit-radial-gradient(top, #383A56, #303133, #303133); - background: -moz-radial-gradient(top, #383A56, #303133, #303133); - background: radial-gradient(to bottom, #383A56, #303133, #303133); } header > h1 { @@ -37,10 +42,6 @@ header > h1 { main { display: flex; flex-direction: row; - width: 1260px; - margin: auto; - max-width: 1260px; - flex: 1 1 100%; margin-bottom: 1rem; overflow-y: hidden; } @@ -198,7 +199,7 @@ article .opengl_canvas_container { width: 100%; text-align: center; display: flex; - flex-direction: row; + flex-direction: column; justify-content: center; border-radius: 3px; } @@ -210,7 +211,7 @@ article .opengl_canvas_container .play_button { height: 128px; position: absolute; top: calc(50% - 56px); - left: calc(50% - 214px); + left: calc(50% - 56px); border: 1px solid white; color: white; background-color: transparent; @@ -222,7 +223,7 @@ article .opengl_canvas_container .play_button { } .opengl_canvas_container canvas { - border-radius: 3px 0px 0px 3px; + border-radius: 3px 3px 0px 0px; border: 1px solid #b0a565; } @@ -256,22 +257,17 @@ article pre { } .opengl_canvas_sidebar { - width: 312px; - height: 480px; border: 1px solid #b0a565; - border-left: none; - - display: flex; - flex-direction: column; - text-align: left; - justify-content: space-between; + border-radius: 0px 0px 3px 3px; + display: grid; + grid-template-columns: repeat(3, 1fr); } .opengl_canvas_sidebar .opengl_value_tracker { + grid-column: 1 span /4; font-size: 14px; - display: flex; - flex-direction: column; - text-align: left; + display: grid; + grid-template-columns: repeat(3, 1fr); } .opengl_canvas_sidebar .opengl_value_tracker > li { -- cgit v1.2.1