From e59f2b152bcd8052b653d7d2c3389b6393c490f4 Mon Sep 17 00:00:00 2001 From: martinfouilleul Date: Fri, 3 Feb 2023 18:44:28 +0100 Subject: [PATCH] simple gles tiled renderer --- build.bat | 3 +- examples/win_canvas/main.c | 30 +++- src/gles_canvas.c | 113 ++++++++++++--- src/gles_canvas_shaders.h | 133 +++++++++++++++++- .../gles_canvas_fragment.glsl | 52 ++++++- src/gles_canvas_shaders/gles_canvas_tile.glsl | 76 ++++++++++ src/milepost.c | 1 + src/platform/win32_clock.c | 38 +++++ src/win32_app.c | 2 +- src/win32_gles_surface.c | 21 ++- 10 files changed, 421 insertions(+), 48 deletions(-) create mode 100644 src/gles_canvas_shaders/gles_canvas_tile.glsl create mode 100644 src/platform/win32_clock.c diff --git a/build.bat b/build.bat index 04a06a4..ed255b8 100644 --- a/build.bat +++ b/build.bat @@ -1,7 +1,8 @@ if not exist bin mkdir bin -call python scripts\embed_text.py src\gles_canvas_shaders\gles_canvas_fragment.glsl src\gles_canvas_shaders\gles_canvas_vertex.glsl --output src\gles_canvas_shaders.h +set gles_shaders=src\gles_canvas_shaders\gles_canvas_fragment.glsl src\gles_canvas_shaders\gles_canvas_vertex.glsl src\gles_canvas_shaders\gles_canvas_tile.glsl +call python scripts\embed_text.py %gles_shaders% --output src\gles_canvas_shaders.h set INCLUDES=/I src /I src/util /I src/platform /I ext /I ext/angle_headers cl /we4013 /Zi /Zc:preprocessor /DMG_IMPLEMENTS_BACKEND_GLES /std:c11 %INCLUDES% /c /Fo:bin/milepost.obj src/milepost.c diff --git a/examples/win_canvas/main.c b/examples/win_canvas/main.c index 67f6e8b..644abc9 100644 --- a/examples/win_canvas/main.c +++ b/examples/win_canvas/main.c @@ -56,8 +56,9 @@ int main() LogLevel(LOG_LEVEL_DEBUG); mp_init(); + mp_clock_init(); //TODO put that in mp_init()? - mp_rect rect = {.x = 100, .y = 100, .w = 800, .h = 600}; + mp_rect rect = {.x = 100, .y = 100, .w = 810, .h = 610}; mp_window window = mp_window_create(rect, "test", 0); //NOTE: create surface @@ -78,11 +79,15 @@ int main() mp_window_focus(window); f32 x = 400, y = 300; -// f32 dx = 5, dy = 5; - f32 dx = 0, dy = 0; + f32 dx = 5, dy = 5; +// f32 dx = 0, dy = 0; + + f64 frameTime = 0; while(!mp_should_quit()) { + f64 startTime = mp_get_time(MP_CLOCK_MONOTONIC); + mp_pump_events(0); mp_event event = {0}; while(mp_next_event(&event)) @@ -163,11 +168,14 @@ int main() mg_circle_fill(x, y, 200); // smile + + f32 frown = frameTime > 0.033 ? 100 : 0; + mg_set_color_rgba(0, 0, 0, 1); mg_set_width(20); mg_move_to(x-100, y-100); - mg_cubic_to(x-50, y-50, x+50, y-50, x+100, y-100); + mg_cubic_to(x-50, y-150+frown, x+50, y-150+frown, x+100, y-100); mg_stroke(); // eyes @@ -179,11 +187,23 @@ int main() mg_set_font(font); mg_set_font_size(12); mg_move_to(50, 50); - mg_text_outlines(str8_lit("Milepost vector graphics test program...")); + + str8 text = str8_pushf(mem_scratch(), + "Milepost vector graphics test program (frame time = %fs, fps = %f)...", + frameTime, + 1./frameTime); + mg_text_outlines(text); mg_fill(); +/* + mg_set_color_rgba(1, 1, 0, 1); + mg_rectangle_fill(8, 8, 100, 50); +*/ mg_flush(); mg_surface_present(surface); + + mem_arena_clear(mem_scratch()); + frameTime = mp_get_time(MP_CLOCK_MONOTONIC) - startTime; } mp_terminate(); diff --git a/src/gles_canvas.c b/src/gles_canvas.c index c807e0f..1865e14 100644 --- a/src/gles_canvas.c +++ b/src/gles_canvas.c @@ -20,7 +20,10 @@ typedef struct mg_gles_canvas_backend GLint dummyVertexBuffer; GLint vertexBuffer; GLint indexBuffer; - GLint program; + GLint tileCounterBuffer; + GLint tileArrayBuffer; + GLint tileProgram; + GLint drawProgram; char* indexMapping; char* vertexMapping; @@ -78,6 +81,9 @@ enum { MG_GLES_CANVAS_DEFAULT_BUFFER_LENGTH = 1<<20, MG_GLES_CANVAS_VERTEX_BUFFER_SIZE = MG_GLES_CANVAS_DEFAULT_BUFFER_LENGTH * LAYOUT_VERTEX_SIZE, MG_GLES_CANVAS_INDEX_BUFFER_SIZE = MG_GLES_CANVAS_DEFAULT_BUFFER_LENGTH * LAYOUT_INT_SIZE, + MG_GLES_CANVAS_TILE_COUNTER_BUFFER_SIZE = 65536, + MG_GLES_CANVAS_TILE_ARRAY_SIZE = 4096, + MG_GLES_CANVAS_TILE_ARRAY_BUFFER_SIZE = MG_GLES_CANVAS_TILE_COUNTER_BUFFER_SIZE * MG_GLES_CANVAS_TILE_ARRAY_SIZE, }; void mg_gles_canvas_update_vertex_layout(mg_gles_canvas_backend* backend) @@ -115,7 +121,6 @@ void mg_gles_canvas_begin(mg_canvas_backend* interface) { return; } - glUseProgram(backend->program); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } @@ -156,11 +161,44 @@ void mg_gles_canvas_draw_batch(mg_canvas_backend* interface, u32 vertexCount, u3 glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->indexBuffer); glUnmapBuffer(GL_SHADER_STORAGE_BUFFER); + mp_rect frame = mg_surface_get_frame(backend->surface); + + const int tileSize = 16; + const int tileCountX = (frame.w + tileSize - 1)/tileSize; + const int tileCountY = (frame.h + tileSize - 1)/tileSize; + const int tileArraySize = 4096; + + //TODO: ensure there's enough space in tile buffer + + //NOTE: we first distribute triangles into tiles: + glUseProgram(backend->tileProgram); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, backend->vertexBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, backend->indexBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, backend->tileCounterBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, backend->tileArrayBuffer); + + glUniform1ui(0, indexCount); + glUniform2ui(1, tileCountX, tileCountY); + glUniform1ui(2, tileSize); + glUniform1ui(3, tileArraySize); + + glDispatchCompute(tileCountX, tileCountY, 1); + + //TODO: then we fire the fragment shader that will select only triangles in its tile +// glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); + + glUseProgram(backend->drawProgram); + glBindBuffer(GL_ARRAY_BUFFER, backend->dummyVertexBuffer); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, backend->vertexBuffer); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, backend->indexBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, backend->tileCounterBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, backend->tileArrayBuffer); - glUniform1i(0, indexCount); + glUniform1ui(0, indexCount); + glUniform2ui(1, tileCountX, tileCountY); + glUniform1ui(2, tileSize); + glUniform1ui(3, tileArraySize); glDrawArrays(GL_TRIANGLES, 0, 6); @@ -235,31 +273,60 @@ mg_canvas_backend* mg_gles_canvas_create(mg_surface surface) glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->indexBuffer); glBufferData(GL_SHADER_STORAGE_BUFFER, MG_GLES_CANVAS_INDEX_BUFFER_SIZE, 0, GL_DYNAMIC_DRAW); + glGenBuffers(1, &backend->tileCounterBuffer); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->tileCounterBuffer); + glBufferData(GL_SHADER_STORAGE_BUFFER, MG_GLES_CANVAS_TILE_COUNTER_BUFFER_SIZE, 0, GL_DYNAMIC_COPY); + + glGenBuffers(1, &backend->tileArrayBuffer); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->tileArrayBuffer); + glBufferData(GL_SHADER_STORAGE_BUFFER, MG_GLES_CANVAS_TILE_ARRAY_BUFFER_SIZE, 0, GL_DYNAMIC_COPY); + glGenBuffers(1, &backend->dummyVertexBuffer); glBindBuffer(GL_ARRAY_BUFFER, backend->dummyVertexBuffer); - unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER); - unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); - backend->program = glCreateProgram(); - - compile_shader(vertexShader, gles_canvas_vertex); - compile_shader(fragmentShader, gles_canvas_fragment); - - glAttachShader(backend->program, vertexShader); - glAttachShader(backend->program, fragmentShader); - glLinkProgram(backend->program); - - int status = 0; - glGetProgramiv(backend->program, GL_LINK_STATUS, &status); - if(!status) + //NOTE: create tile program { - char buffer[256]; - int size = 0; - glGetProgramInfoLog(backend->program, 256, &size, buffer); - printf("link error: %.*s\n", size, buffer); - } + GLuint tileShader = glCreateShader(GL_COMPUTE_SHADER); + backend->tileProgram = glCreateProgram(); - glUseProgram(backend->program); + compile_shader(tileShader, gles_canvas_tile); + + glAttachShader(backend->tileProgram, tileShader); + glLinkProgram(backend->tileProgram); + + int status = 0; + glGetProgramiv(backend->tileProgram, GL_LINK_STATUS, &status); + if(!status) + { + char buffer[256]; + int size = 0; + glGetProgramInfoLog(backend->tileProgram, 256, &size, buffer); + printf("link error: %.*s\n", size, buffer); + } + } + //NOTE: create draw program + { + GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); + GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + backend->drawProgram = glCreateProgram(); + + compile_shader(vertexShader, gles_canvas_vertex); + compile_shader(fragmentShader, gles_canvas_fragment); + + glAttachShader(backend->drawProgram, vertexShader); + glAttachShader(backend->drawProgram, fragmentShader); + glLinkProgram(backend->drawProgram); + + int status = 0; + glGetProgramiv(backend->drawProgram, GL_LINK_STATUS, &status); + if(!status) + { + char buffer[256]; + int size = 0; + glGetProgramInfoLog(backend->drawProgram, 256, &size, buffer); + printf("link error: %.*s\n", size, buffer); + } + } mg_gles_canvas_update_vertex_layout(backend); } diff --git a/src/gles_canvas_shaders.h b/src/gles_canvas_shaders.h index ac4d83b..9cce15f 100644 --- a/src/gles_canvas_shaders.h +++ b/src/gles_canvas_shaders.h @@ -2,7 +2,7 @@ * * file: gles_canvas_shaders.h * note: string literals auto-generated by embed_text.py -* date: 02/022023 +* date: 03/022023 * **********************************************************************/ #ifndef __GLES_CANVAS_SHADERS_H__ @@ -33,10 +33,22 @@ const char* gles_canvas_fragment = " uint elements[];\n" "} indexBuffer ;\n" "\n" -"layout(location = 0) uniform int indexCount;\n" +"layout(binding = 2) coherent buffer tileCounterBufferSSBO {\n" +" uint elements[];\n" +"} tileCounterBuffer ;\n" +"\n" +"layout(binding = 3) coherent buffer tileArrayBufferSSBO {\n" +" uint elements[];\n" +"} tileArrayBuffer ;\n" +"\n" +"layout(location = 0) uniform uint indexCount;\n" +"layout(location = 1) uniform uvec2 tileCount;\n" +"layout(location = 2) uniform uint tileSize;\n" +"layout(location = 3) uniform uint tileArraySize;\n" "\n" "layout(location = 0) out vec4 fragColor;\n" "\n" +"\n" "bool is_top_left(ivec2 a, ivec2 b)\n" "{\n" " return( (a.y == b.y && b.x < a.x)\n" @@ -50,6 +62,10 @@ const char* gles_canvas_fragment = "\n" "void main()\n" "{\n" +" uvec2 tileCoord = uvec2(gl_FragCoord.xy)/tileSize;\n" +" uint tileIndex = tileCoord.y * tileCount.x + tileCoord.x;\n" +" uint tileCounter = tileCounterBuffer.elements[tileIndex];\n" +"\n" " const float subPixelFactor = 16.;\n" " const int sampleCount = 8;\n" "\n" @@ -63,6 +79,30 @@ const char* gles_canvas_fragment = " centerPoint + ivec2(3, -7),\n" " centerPoint + ivec2(7, 7));\n" "\n" +"\n" +" //DEBUG\n" +"/*\n" +" if( int(gl_FragCoord.x - 0.5) % 16 == 0\n" +" ||int(gl_FragCoord.y - 0.5) % 16 == 0)\n" +" {\n" +" fragColor = vec4(0, 0, 0, 1);\n" +" }\n" +" else if(tileCounterBuffer.elements[tileIndex] == 2u)\n" +" {\n" +" fragColor = vec4(1, 1, 0, 1);\n" +" }\n" +" else if(tileCounter != 0u)\n" +" {\n" +" fragColor = vec4(0, 1, 0, 1);\n" +" }\n" +" else\n" +" {\n" +" fragColor = vec4(1, 0, 0, 1);\n" +" }\n" +" return;\n" +"//*/\n" +" //----\n" +"\n" " vec4 sampleColor[sampleCount];\n" " vec4 currentColor[sampleCount];\n" " int currentZIndex[sampleCount];\n" @@ -76,11 +116,13 @@ const char* gles_canvas_fragment = " currentColor[i] = vec4(0, 0, 0, 0);\n" " }\n" "\n" -" for(int triangleIndex=0; triangleIndex tileBox.z\n" +" || bbox.z < tileBox.x\n" +" || bbox.y > tileBox.w\n" +" || bbox.w < tileBox.y))\n" +" {\n" +" tileArrayBuffer.elements[tileArrayOffset + tileCounter] = triangleIndex;\n" +" tileCounter++;\n" +" }\n" +" }\n" +" tileCounterBuffer.elements[tileIndex] = tileCounter;\n" +"\n" +"// tileCounterBuffer.elements[tileIndex] = 1u;\n" +"}\n"; + #endif // __GLES_CANVAS_SHADERS_H__ diff --git a/src/gles_canvas_shaders/gles_canvas_fragment.glsl b/src/gles_canvas_shaders/gles_canvas_fragment.glsl index 2ef0898..ca1b0b6 100644 --- a/src/gles_canvas_shaders/gles_canvas_fragment.glsl +++ b/src/gles_canvas_shaders/gles_canvas_fragment.glsl @@ -20,10 +20,22 @@ layout(binding = 1) buffer indexBufferSSBO { uint elements[]; } indexBuffer ; -layout(location = 0) uniform int indexCount; +layout(binding = 2) coherent buffer tileCounterBufferSSBO { + uint elements[]; +} tileCounterBuffer ; + +layout(binding = 3) coherent buffer tileArrayBufferSSBO { + uint elements[]; +} tileArrayBuffer ; + +layout(location = 0) uniform uint indexCount; +layout(location = 1) uniform uvec2 tileCount; +layout(location = 2) uniform uint tileSize; +layout(location = 3) uniform uint tileArraySize; layout(location = 0) out vec4 fragColor; + bool is_top_left(ivec2 a, ivec2 b) { return( (a.y == b.y && b.x < a.x) @@ -37,6 +49,10 @@ int orient2d(ivec2 a, ivec2 b, ivec2 p) void main() { + uvec2 tileCoord = uvec2(gl_FragCoord.xy)/tileSize; + uint tileIndex = tileCoord.y * tileCount.x + tileCoord.x; + uint tileCounter = tileCounterBuffer.elements[tileIndex]; + const float subPixelFactor = 16.; const int sampleCount = 8; @@ -50,6 +66,30 @@ void main() centerPoint + ivec2(3, -7), centerPoint + ivec2(7, 7)); + + //DEBUG +/* + if( int(gl_FragCoord.x - 0.5) % 16 == 0 + ||int(gl_FragCoord.y - 0.5) % 16 == 0) + { + fragColor = vec4(0, 0, 0, 1); + } + else if(tileCounterBuffer.elements[tileIndex] == 2u) + { + fragColor = vec4(1, 1, 0, 1); + } + else if(tileCounter != 0u) + { + fragColor = vec4(0, 1, 0, 1); + } + else + { + fragColor = vec4(1, 0, 0, 1); + } + return; +//*/ + //---- + vec4 sampleColor[sampleCount]; vec4 currentColor[sampleCount]; int currentZIndex[sampleCount]; @@ -63,11 +103,13 @@ void main() currentColor[i] = vec4(0, 0, 0, 0); } - for(int triangleIndex=0; triangleIndex tileBox.z + || bbox.z < tileBox.x + || bbox.y > tileBox.w + || bbox.w < tileBox.y)) + { + tileArrayBuffer.elements[tileArrayOffset + tileCounter] = triangleIndex; + tileCounter++; + } + } + tileCounterBuffer.elements[tileIndex] = tileCounter; + +// tileCounterBuffer.elements[tileIndex] = 1u; +} diff --git a/src/milepost.c b/src/milepost.c index 287580f..5a6b16c 100644 --- a/src/milepost.c +++ b/src/milepost.c @@ -24,6 +24,7 @@ #if defined(OS_WIN64) #include"platform/win32_base_allocator.c" + #include"platform/win32_clock.c" //TODO #elif defined(OS_MACOS) #include"platform/unix_base_allocator.c" diff --git a/src/platform/win32_clock.c b/src/platform/win32_clock.c new file mode 100644 index 0000000..3e985f3 --- /dev/null +++ b/src/platform/win32_clock.c @@ -0,0 +1,38 @@ +/************************************************************//** +* +* @file: win32_clock.c +* @author: Martin Fouilleul +* @date: 03/02/2023 +* @revision: +* +*****************************************************************/ +#include + +#include"typedefs.h" +#include"platform_clock.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static u64 __performanceCounterFreq = 0; + +void mp_clock_init() +{ + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + __performanceCounterFreq = freq.QuadPart; +} + +f64 mp_get_time(mp_clock_kind clock) +{ + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + + f64 time = __performanceCounterFreq ? (counter.QuadPart / (f64)__performanceCounterFreq) : 0; + return(time); +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/win32_app.c b/src/win32_app.c index be565c1..2147839 100644 --- a/src/win32_app.c +++ b/src/win32_app.c @@ -518,7 +518,7 @@ mp_window mp_window_create(mp_rect rect, const char* title, mp_window_style styl HWND windowHandle = CreateWindow("ApplicationWindowClass", "Test Window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, - 800, 600, + rect.w, rect.h, 0, 0, windowClass.hInstance, 0); if(!windowHandle) diff --git a/src/win32_gles_surface.c b/src/win32_gles_surface.c index a183286..22a6600 100644 --- a/src/win32_gles_surface.c +++ b/src/win32_gles_surface.c @@ -41,21 +41,27 @@ void mg_gles_surface_prepare(mg_surface_data* interface) void mg_gles_surface_present(mg_surface_data* interface) { - - //TODO: eglSwapBuffers seem to never block in macOS (ie eglSwapInterval doesn't seem to have any effect) - // We need to use a CVDisplayLink to time this if we want surface present to block - mg_gles_surface* surface = (mg_gles_surface*)interface; eglSwapBuffers(surface->eglDisplay, surface->eglSurface); } /* void mg_gles_surface_set_frame(mg_surface_data* interface, mp_rect frame); -mp_rect mg_gles_surface_get_frame(mg_surface_data* interface); + void mg_gles_surface_set_hidden(mg_surface_data* interface, bool hidden); bool mg_gles_surface_get_hidden(mg_surface_data* interface); */ +mp_rect mg_gles_surface_get_frame(mg_surface_data* interface) +{ + mg_gles_surface* surface = (mg_gles_surface*)interface; + RECT rect = {0}; + GetClientRect(surface->hWnd, &rect); + + mp_rect res = {rect.left, rect.bottom, rect.right - rect.left, rect.bottom - rect.top}; + return(res); +} + mg_surface mg_gles_surface_create_for_window(mp_window window) { mg_surface res = mg_surface_nil(); @@ -69,8 +75,8 @@ mg_surface mg_gles_surface_create_for_window(mp_window window) surface->interface.destroy = mg_gles_surface_destroy; surface->interface.prepare = mg_gles_surface_prepare; surface->interface.present = mg_gles_surface_present; - /*TODO surface->interface.getFrame = mg_gles_surface_get_frame; + /*TODO surface->interface.setFrame = mg_gles_surface_set_frame; surface->interface.getHidden = mg_gles_surface_get_hidden; surface->interface.setHidden = mg_gles_surface_set_hidden; @@ -117,7 +123,8 @@ mg_surface mg_gles_surface_create_for_window(mp_window window) surface->eglContext = eglCreateContext(surface->eglDisplay, surface->eglConfig, EGL_NO_CONTEXT, contextAttributes); eglMakeCurrent(surface->eglDisplay, surface->eglSurface, surface->eglSurface, surface->eglContext); - eglSwapInterval(surface->eglDisplay, 1); +//TODO: reactivate this when finished testing! +// eglSwapInterval(surface->eglDisplay, 1); res = mg_surface_alloc_handle((mg_surface_data*)surface); }