Fixed indexing in gles_canvas_fragment shaders and fixed native keys buffer

This commit is contained in:
martinfouilleul 2023-02-01 16:23:51 +01:00
parent 8e87837fcc
commit e0300e9e3c
23 changed files with 6835 additions and 6454 deletions

View File

@ -1,6 +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 INCLUDES=/I src /I src/util /I src/platform /I ext /I ext/angle_headers
cl /we4013 /Zi /Zc:preprocessor /std:c11 %INCLUDES% /c /Fo:bin/milepost.obj src/milepost.c
cl /we4013 /Zi /Zc:preprocessor /DMG_IMPLEMENTS_BACKEND_GLES /std:c11 %INCLUDES% /c /Fo:bin/milepost.obj src/milepost.c
lib bin/milepost.obj /OUT:bin/milepost.lib

View File

@ -0,0 +1,3 @@
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% main.c /link /LIBPATH:../../bin milepost.lib /LIBPATH:../../bin libEGL.dll.lib libGLESv2.dll.lib user32.lib opengl32.lib gdi32.lib /out:../../bin/example_canvas.exe

View File

@ -1,144 +1,153 @@
/************************************************************//**
*
* @file: main.cpp
* @author: Martin Fouilleul
* @date: 30/07/2022
* @revision:
*
*****************************************************************/
#include<stdlib.h>
#include<string.h>
#define _USE_MATH_DEFINES //NOTE: necessary for MSVC
#include<math.h>
#include"milepost.h"
#include"metal_surface.h"
#define LOG_SUBSYSTEM "Main"
int main()
{
LogLevel(LOG_LEVEL_DEBUG);
mp_init();
mp_rect rect = {.x = 100, .y = 100, .w = 800, .h = 600};
mp_window window = mp_window_create(rect, "test", 0);
//NOTE: create surface
mg_surface surface = mg_metal_surface_create_for_window(window);
//TODO: create canvas
mg_canvas canvas = mg_canvas_create(surface);
// start app
mp_window_bring_to_front(window);
mp_window_focus(window);
while(!mp_should_quit())
{
mp_pump_events(0);
mp_event event = {0};
while(mp_next_event(&event))
{
switch(event.type)
{
case MP_EVENT_WINDOW_CLOSE:
{
mp_request_quit();
} break;
case MP_EVENT_WINDOW_RESIZE:
{
printf("resized, rect = {%f, %f, %f, %f}\n",
event.frame.rect.x,
event.frame.rect.y,
event.frame.rect.w,
event.frame.rect.h);
} break;
case MP_EVENT_WINDOW_MOVE:
{
printf("moved, rect = {%f, %f, %f, %f}\n",
event.frame.rect.x,
event.frame.rect.y,
event.frame.rect.w,
event.frame.rect.h);
} break;
case MP_EVENT_MOUSE_MOVE:
{
printf("mouse moved, pos = {%f, %f}, delta = {%f, %f}\n",
event.move.x,
event.move.y,
event.move.deltaX,
event.move.deltaY);
} break;
case MP_EVENT_MOUSE_WHEEL:
{
printf("mouse wheel, delta = {%f, %f}\n",
event.move.deltaX,
event.move.deltaY);
} break;
case MP_EVENT_MOUSE_ENTER:
{
printf("mouse enter\n");
} break;
case MP_EVENT_MOUSE_LEAVE:
{
printf("mouse leave\n");
} break;
case MP_EVENT_MOUSE_BUTTON:
{
printf("mouse button %i: %i\n",
event.key.code,
event.key.action == MP_KEY_PRESS ? 1 : 0);
} break;
case MP_EVENT_KEYBOARD_KEY:
{
printf("key %i: %s\n",
event.key.code,
event.key.action == MP_KEY_PRESS ? "press" : (event.key.action == MP_KEY_RELEASE ? "release" : "repeat"));
} break;
case MP_EVENT_KEYBOARD_CHAR:
{
printf("entered char %s\n", event.character.sequence);
} break;
default:
break;
}
}
mg_surface_prepare(surface);
mg_set_color_rgba(1, 0, 1, 1);
mg_clear();
mg_set_color_rgba(1, 1, 0, 1);
mg_circle_fill(400, 300, 200);
mg_set_color_rgba(0, 0, 0, 1);
mg_set_width(20);
mg_move_to(300, 200);
mg_cubic_to(350, 150, 450, 150, 500, 200);
mg_stroke();
mg_ellipse_fill(330, 350, 30, 50);
mg_ellipse_fill(470, 350, 30, 50);
mg_flush();
mg_surface_present(surface);
}
mp_terminate();
return(0);
}
/************************************************************//**
*
* @file: main.cpp
* @author: Martin Fouilleul
* @date: 30/07/2022
* @revision:
*
*****************************************************************/
#include<stdlib.h>
#include<string.h>
#define _USE_MATH_DEFINES //NOTE: necessary for MSVC
#include<math.h>
#include"milepost.h"
#define LOG_SUBSYSTEM "Main"
int main()
{
LogLevel(LOG_LEVEL_DEBUG);
mp_init();
mp_rect rect = {.x = 100, .y = 100, .w = 800, .h = 600};
mp_window window = mp_window_create(rect, "test", 0);
//NOTE: create surface
#if defined(OS_MACOS)
mg_surface surface = mg_metal_surface_create_for_window(window);
#elif defined(OS_WIN64)
mg_surface surface = mg_gles_surface_create_for_window(window);
#else
#error "unsupported OS"
#endif
//TODO: create canvas
mg_canvas canvas = mg_canvas_create(surface);
// start app
mp_window_bring_to_front(window);
mp_window_focus(window);
while(!mp_should_quit())
{
mp_pump_events(0);
mp_event event = {0};
while(mp_next_event(&event))
{
switch(event.type)
{
case MP_EVENT_WINDOW_CLOSE:
{
mp_request_quit();
} break;
case MP_EVENT_WINDOW_RESIZE:
{
printf("resized, rect = {%f, %f, %f, %f}\n",
event.frame.rect.x,
event.frame.rect.y,
event.frame.rect.w,
event.frame.rect.h);
} break;
case MP_EVENT_WINDOW_MOVE:
{
printf("moved, rect = {%f, %f, %f, %f}\n",
event.frame.rect.x,
event.frame.rect.y,
event.frame.rect.w,
event.frame.rect.h);
} break;
case MP_EVENT_MOUSE_MOVE:
{
printf("mouse moved, pos = {%f, %f}, delta = {%f, %f}\n",
event.move.x,
event.move.y,
event.move.deltaX,
event.move.deltaY);
} break;
case MP_EVENT_MOUSE_WHEEL:
{
printf("mouse wheel, delta = {%f, %f}\n",
event.move.deltaX,
event.move.deltaY);
} break;
case MP_EVENT_MOUSE_ENTER:
{
printf("mouse enter\n");
} break;
case MP_EVENT_MOUSE_LEAVE:
{
printf("mouse leave\n");
} break;
case MP_EVENT_MOUSE_BUTTON:
{
printf("mouse button %i: %i\n",
event.key.code,
event.key.action == MP_KEY_PRESS ? 1 : 0);
} break;
case MP_EVENT_KEYBOARD_KEY:
{
printf("key %i: %s\n",
event.key.code,
event.key.action == MP_KEY_PRESS ? "press" : (event.key.action == MP_KEY_RELEASE ? "release" : "repeat"));
} break;
case MP_EVENT_KEYBOARD_CHAR:
{
printf("entered char %s\n", event.character.sequence);
} break;
default:
break;
}
}
mg_surface_prepare(surface);
// background
mg_set_color_rgba(1, 0, 1, 1);
mg_clear();
// head
mg_set_color_rgba(1, 1, 0, 1);
mg_circle_fill(400, 300, 200);
// smile
mg_set_color_rgba(1, 0, 0, 1);
mg_set_width(20);
mg_move_to(300, 200);
mg_cubic_to(350, 150, 450, 150, 500, 200);
mg_stroke();
// eyes
mg_ellipse_fill(330, 350, 30, 50);
mg_ellipse_fill(470, 350, 30, 50);
mg_flush();
mg_surface_present(surface);
}
mp_terminate();
return(0);
}

View File

@ -1,2 +1,2 @@
set INCLUDES=/I ..\..\src /I ..\..\src\util /I ..\..\src\platform /I ../../ext /I ../../ext/angle_headers
cl /we4013 /Zi /Zc:preprocessor /std:c11 %INCLUDES% main.c /link /LIBPATH:../../bin milepost.lib /LIBPATH:./ libEGL.dll.lib libGLESv2.dll.lib user32.lib opengl32.lib gdi32.lib /out:test.exe
cl /we4013 /Zi /Zc:preprocessor /std:c11 %INCLUDES% main.c /link /LIBPATH:../../bin milepost.lib /LIBPATH:../../bin libEGL.dll.lib libGLESv2.dll.lib user32.lib opengl32.lib gdi32.lib /out:../../bin/example_gles_triangle.exe

View File

@ -0,0 +1,3 @@
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% main.c /link /LIBPATH:../../bin milepost.lib /LIBPATH:../../bin libEGL.dll.lib libGLESv2.dll.lib user32.lib opengl32.lib gdi32.lib /out:../../bin/example_canvas.exe

View File

@ -0,0 +1,11 @@
#!/bin/bash
BINDIR=../../bin
RESDIR=../../resources
SRCDIR=../../src
INCLUDES="-I$SRCDIR -I$SRCDIR/util -I$SRCDIR/platform -I$SRCDIR/app"
LIBS="-L$BINDIR -lmilepost -framework Carbon -framework Cocoa -framework Metal -framework QuartzCore"
FLAGS="-mmacos-version-min=10.15.4 -DDEBUG -DLOG_COMPILE_DEBUG -Wl,-dead_strip"
clang -g $FLAGS $LIBS $INCLUDES -o $BINDIR/example_canvas main.c

136
examples/win_canvas/main.c Normal file
View File

@ -0,0 +1,136 @@
/************************************************************//**
*
* @file: main.cpp
* @author: Martin Fouilleul
* @date: 30/07/2022
* @revision:
*
*****************************************************************/
#include<stdlib.h>
#include<string.h>
#define _USE_MATH_DEFINES //NOTE: necessary for MSVC
#include<math.h>
#include"milepost.h"
#define LOG_SUBSYSTEM "Main"
int main()
{
LogLevel(LOG_LEVEL_DEBUG);
mp_init();
mp_rect rect = {.x = 100, .y = 100, .w = 800, .h = 600};
mp_window window = mp_window_create(rect, "test", 0);
//NOTE: create surface
#if defined(OS_MACOS)
mg_surface surface = mg_metal_surface_create_for_window(window);
#elif defined(OS_WIN64)
mg_surface surface = mg_gles_surface_create_for_window(window);
#else
#error "unsupported OS"
#endif
//TODO: create canvas
mg_canvas canvas = mg_canvas_create(surface);
// start app
mp_window_bring_to_front(window);
mp_window_focus(window);
f32 dx = 17.000029, dy = 0;
while(!mp_should_quit())
{
mp_pump_events(0);
mp_event event = {0};
while(mp_next_event(&event))
{
switch(event.type)
{
case MP_EVENT_WINDOW_CLOSE:
{
mp_request_quit();
} break;
case MP_EVENT_WINDOW_RESIZE:
{
printf("resized, rect = {%f, %f, %f, %f}\n",
event.frame.rect.x,
event.frame.rect.y,
event.frame.rect.w,
event.frame.rect.h);
} break;
case MP_EVENT_KEYBOARD_KEY:
{
printf("key %i: %s\n",
event.key.code,
event.key.action == MP_KEY_PRESS ? "press" : (event.key.action == MP_KEY_RELEASE ? "release" : "repeat"));
if(event.key.action == MP_KEY_PRESS || event.key.action == MP_KEY_REPEAT)
{
if(event.key.code == MP_KEY_LEFT)
{
printf("left\n");
dx-=0.1;
}
else if(event.key.code == MP_KEY_RIGHT)
{
printf("right\n");
dx+=0.1;
}
else if(event.key.code == MP_KEY_UP)
{
printf("up\n");
dy+=0.1;
}
else if(event.key.code == MP_KEY_DOWN)
{
printf("down\n");
dy-=0.1;
}
}
} break;
default:
break;
}
}
mg_surface_prepare(surface);
printf("dx = %f, dy = %f\n", dx, dy);
// background
mg_set_color_rgba(1, 0, 1, 1);
mg_clear();
/*
// head
mg_set_color_rgba(1, 1, 0, 1);
mg_circle_fill(dx+400, dy+300, 200);
// smile
mg_set_color_rgba(0, 0, 0, 1);
mg_set_width(20);
mg_move_to(dx+300, dy+200);
mg_cubic_to(dx+350, dy+150, dx+450, dy+150, dx+500, dy+200);
mg_stroke();
// eyes
mg_ellipse_fill(dx+330, dy+350, 30, 50);
mg_ellipse_fill(dx+470, dy+350, 30, 50);
*/
mg_rectangle_fill((int)(dx + 200), 200, (int)(dy+300), (int)(dy+300));
mg_flush();
mg_surface_present(surface);
}
mp_terminate();
return(0);
}

View File

@ -38,41 +38,19 @@ mg_gles_surface* mg_gles_canvas_get_surface(mg_gles_canvas_backend* canvas)
return(res);
}
void mg_gles_canvas_draw_buffers(mg_canvas_backend* interface, u32 vertexCount, u32 indexCount, mg_color clearColor)
//NOTE: debugger
typedef struct debug_vertex
{
mg_gles_canvas_backend* backend = (mg_gles_canvas_backend*)interface;
mg_gles_surface* surface = mg_gles_canvas_get_surface(backend);
if(!surface)
{
return;
}
//WARN: dummy test code
indexCount = 3;
*(vec2*)(interface->vertexLayout.posBuffer) = (vec2){400, 300};
*(vec2*)(interface->vertexLayout.posBuffer + interface->vertexLayout.posStride) = (vec2){450, 300};
*(vec2*)(interface->vertexLayout.posBuffer + 2*interface->vertexLayout.posStride) = (vec2){400, 350};
for(int i=0; i<3; i++)
{
*(vec4*)(interface->vertexLayout.cubicBuffer + i*interface->vertexLayout.cubicStride) = (vec4){1, 1, 1, 1};
*(vec2*)(interface->vertexLayout.uvBuffer + i*interface->vertexLayout.uvStride) = (vec2){0, 0};
*(vec4*)(interface->vertexLayout.colorBuffer + i*interface->vertexLayout.colorStride) = (vec4){1, 0, 0, 1};
*(vec4*)(interface->vertexLayout.clipBuffer + i*interface->vertexLayout.clipStride) = (vec4){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX};
*(u32*)(interface->vertexLayout.zIndexBuffer + i*interface->vertexLayout.zIndexStride) = 1;
*(u32*)(interface->vertexLayout.indexBuffer + i*interface->vertexLayout.indexStride) = i;
}
// end dummy test code
glUseProgram(backend->program);
glBindBuffer(GL_ARRAY_BUFFER, backend->dummyVertexBuffer);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, backend->vertexBuffer);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, backend->indexBuffer);
glUniform1i(0, indexCount);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
vec2 pos;
u8 align0[8];
vec4 cubic;
vec2 uv;
u8 align1[8];
vec4 color;
vec4 clip;
int zIndex;
u8 align2[12];
} debug_vertex;
#define LayoutNext(prevName, prevType, nextType) \
AlignUpOnPow2(_cat3_(LAYOUT_, prevName, _OFFSET)+_cat3_(LAYOUT_, prevType, _SIZE), _cat3_(LAYOUT_, nextType, _ALIGN))
@ -104,17 +82,9 @@ enum {
void mg_gles_canvas_update_vertex_layout(mg_gles_canvas_backend* backend)
{
if(backend->vertexMapping)
{
glUnmapBuffer(backend->vertexBuffer);
}
glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->vertexBuffer);
backend->vertexMapping = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, MG_GLES_CANVAS_VERTEX_BUFFER_SIZE, GL_MAP_WRITE_BIT);
if(backend->indexMapping)
{
free(backend->indexMapping);
}
glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->indexBuffer);
backend->indexMapping = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, MG_GLES_CANVAS_INDEX_BUFFER_SIZE, GL_MAP_WRITE_BIT);
@ -137,6 +107,35 @@ void mg_gles_canvas_update_vertex_layout(mg_gles_canvas_backend* backend)
.indexStride = LAYOUT_INT_SIZE};
}
void mg_gles_canvas_draw_buffers(mg_canvas_backend* interface, u32 vertexCount, u32 indexCount, mg_color clearColor)
{
mg_gles_canvas_backend* backend = (mg_gles_canvas_backend*)interface;
mg_gles_surface* surface = mg_gles_canvas_get_surface(backend);
if(!surface)
{
return;
}
/*NOTE: if we want debug_vertex while debugging, the following ensures the struct def doesn't get stripped away
debug_vertex vertex;
printf("foo %p\n", &vertex);
//*/
glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->vertexBuffer);
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->indexBuffer);
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
glUseProgram(backend->program);
glBindBuffer(GL_ARRAY_BUFFER, backend->dummyVertexBuffer);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, backend->vertexBuffer);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, backend->indexBuffer);
glUniform1i(0, indexCount);
glDrawArrays(GL_TRIANGLES, 0, 6);
mg_gles_canvas_update_vertex_layout(backend);
}
void mg_gles_canvas_destroy(mg_canvas_backend* interface)
{
mg_gles_canvas_backend* backend = (mg_gles_canvas_backend*)interface;

View File

@ -2,7 +2,7 @@
*
* file: gles_canvas_shaders.h
* note: string literals auto-generated by embed_text.py
* date: 31/012023
* date: 01/022023
*
**********************************************************************/
#ifndef __GLES_CANVAS_SHADERS_H__
@ -30,15 +30,112 @@ const char* gles_canvas_fragment =
"} vertexBuffer ;\n"
"\n"
"layout(binding = 1) buffer indexBufferSSBO {\n"
" vec2 elements[];\n"
" uint elements[];\n"
"} indexBuffer ;\n"
"\n"
"layout(location = 0) uniform int indexCount;\n"
"layout(location = 0) out vec4 fragColor;\n"
"\n"
"bool is_top_left(vec2 a, vec2 b)\n"
"{\n"
" return( (a.y == b.y && b.x < a.x)\n"
" ||(b.y < a.y));\n"
"}\n"
"\n"
"float orient2d(vec2 a, vec2 b, vec2 c)\n"
"{\n"
" //////////////////////////////////////////////////////////////////////////////////////////\n"
" //TODO(martin): FIX this. This is a **horrible** quick hack to fix the precision issues\n"
" // arising when a, b, and c are close. But it degrades when a, c, and c\n"
" // are big. The proper solution is to change the expression to avoid\n"
" // precision loss but I'm too busy/lazy to do it now.\n"
" //////////////////////////////////////////////////////////////////////////////////////////\n"
" a *= 10.;\n"
" b *= 10.;\n"
" c *= 10.;\n"
" return((b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x));\n"
"}\n"
"\n"
"void main()\n"
"{\n"
" fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
" vec4 pixelColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
" vec4 currentColor = vec4(0., 0., 0., 1.0);\n"
"\n"
" vec2 samplePoint = gl_FragCoord.xy;\n"
"\n"
" int currentZIndex = -1;\n"
" int flipCount = 0;\n"
"\n"
"\n"
" for(int i=0; i<indexCount; i+=3)\n"
" {\n"
" uint i0 = indexBuffer.elements[i];\n"
" uint i1 = indexBuffer.elements[i+1];\n"
" uint i2 = indexBuffer.elements[i+2];\n"
"\n"
" vec2 p0 = vertexBuffer.elements[i0].pos;\n"
" vec2 p1 = vertexBuffer.elements[i1].pos;\n"
" vec2 p2 = vertexBuffer.elements[i2].pos;\n"
"\n"
" int zIndex = vertexBuffer.elements[i0].zIndex;\n"
" vec4 color = vertexBuffer.elements[i0].color;\n"
"\n"
" //NOTE(martin): reorder triangle counter-clockwise and compute bias for each edge\n"
" float cw = (p1 - p0).x*(p2 - p0).y - (p1 - p0).y*(p2 - p0).x;\n"
" if(cw < 0.)\n"
" {\n"
" uint tmpIndex = i1;\n"
" i1 = i2;\n"
" i2 = tmpIndex;\n"
"\n"
" vec2 tmpPoint = p1;\n"
" p1 = p2;\n"
" p2 = tmpPoint;\n"
" }\n"
"\n"
" vec4 cubic0 = vertexBuffer.elements[i0].cubic;\n"
" vec4 cubic1 = vertexBuffer.elements[i1].cubic;\n"
" vec4 cubic2 = vertexBuffer.elements[i2].cubic;\n"
"\n"
" int bias0 = is_top_left(p1, p2) ? 0 : -1;\n"
" int bias1 = is_top_left(p2, p0) ? 0 : -1;\n"
" int bias2 = is_top_left(p0, p1) ? 0 : -1;\n"
"\n"
" float w0 = orient2d(p1, p2, samplePoint);\n"
" float w1 = orient2d(p2, p0, samplePoint);\n"
" float w2 = orient2d(p0, p1, samplePoint);\n"
"\n"
" if((int(w0)+bias0) >= 0 && (int(w1)+bias1) >= 0 && (int(w2)+bias2) >= 0)\n"
" {\n"
" //TODO check cubic\n"
" vec4 cubic = (cubic0*w0 + cubic1*w1 + cubic2*w2)/(w0+w1+w2);\n"
"\n"
" float eps = 0.0001;\n"
" if(cubic.w*(cubic.x*cubic.x*cubic.x - cubic.y*cubic.z) <= eps)\n"
" {\n"
" if(zIndex == currentZIndex)\n"
" {\n"
" flipCount++;\n"
" }\n"
" else\n"
" {\n"
" if((flipCount & 0x01) != 0)\n"
" {\n"
" pixelColor = currentColor;\n"
" }\n"
" currentColor = pixelColor*(1.-color.a) + color.a*color;\n"
" currentZIndex = zIndex;\n"
" flipCount = 1;\n"
" }\n"
" }\n"
" }\n"
" }\n"
" if((flipCount & 0x01) != 0)\n"
" {\n"
" pixelColor = currentColor;\n"
" }\n"
"\n"
" fragColor = pixelColor;\n"
"}\n";
//NOTE: string imported from src\gles_canvas_shaders\gles_canvas_vertex.glsl

View File

@ -17,13 +17,110 @@ layout(binding = 0) buffer vertexBufferSSBO {
} vertexBuffer ;
layout(binding = 1) buffer indexBufferSSBO {
vec2 elements[];
uint elements[];
} indexBuffer ;
layout(location = 0) uniform int indexCount;
layout(location = 0) out vec4 fragColor;
bool is_top_left(vec2 a, vec2 b)
{
return( (a.y == b.y && b.x < a.x)
||(b.y < a.y));
}
float orient2d(vec2 a, vec2 b, vec2 c)
{
//////////////////////////////////////////////////////////////////////////////////////////
//TODO(martin): FIX this. This is a **horrible** quick hack to fix the precision issues
// arising when a, b, and c are close. But it degrades when a, c, and c
// are big. The proper solution is to change the expression to avoid
// precision loss but I'm too busy/lazy to do it now.
//////////////////////////////////////////////////////////////////////////////////////////
a *= 10.;
b *= 10.;
c *= 10.;
return((b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x));
}
void main()
{
fragColor = vec4(0.0, 1.0, 0.0, 1.0);
vec4 pixelColor = vec4(0.0, 1.0, 0.0, 1.0);
vec4 currentColor = vec4(0., 0., 0., 1.0);
vec2 samplePoint = gl_FragCoord.xy;
int currentZIndex = -1;
int flipCount = 0;
for(int i=0; i<indexCount; i+=3)
{
uint i0 = indexBuffer.elements[i];
uint i1 = indexBuffer.elements[i+1];
uint i2 = indexBuffer.elements[i+2];
vec2 p0 = vertexBuffer.elements[i0].pos;
vec2 p1 = vertexBuffer.elements[i1].pos;
vec2 p2 = vertexBuffer.elements[i2].pos;
int zIndex = vertexBuffer.elements[i0].zIndex;
vec4 color = vertexBuffer.elements[i0].color;
//NOTE(martin): reorder triangle counter-clockwise and compute bias for each edge
float cw = (p1 - p0).x*(p2 - p0).y - (p1 - p0).y*(p2 - p0).x;
if(cw < 0.)
{
uint tmpIndex = i1;
i1 = i2;
i2 = tmpIndex;
vec2 tmpPoint = p1;
p1 = p2;
p2 = tmpPoint;
}
vec4 cubic0 = vertexBuffer.elements[i0].cubic;
vec4 cubic1 = vertexBuffer.elements[i1].cubic;
vec4 cubic2 = vertexBuffer.elements[i2].cubic;
int bias0 = is_top_left(p1, p2) ? 0 : -1;
int bias1 = is_top_left(p2, p0) ? 0 : -1;
int bias2 = is_top_left(p0, p1) ? 0 : -1;
float w0 = orient2d(p1, p2, samplePoint);
float w1 = orient2d(p2, p0, samplePoint);
float w2 = orient2d(p0, p1, samplePoint);
if((int(w0)+bias0) >= 0 && (int(w1)+bias1) >= 0 && (int(w2)+bias2) >= 0)
{
//TODO check cubic
vec4 cubic = (cubic0*w0 + cubic1*w1 + cubic2*w2)/(w0+w1+w2);
float eps = 0.0001;
if(cubic.w*(cubic.x*cubic.x*cubic.x - cubic.y*cubic.z) <= eps)
{
if(zIndex == currentZIndex)
{
flipCount++;
}
else
{
if((flipCount & 0x01) != 0)
{
pixelColor = currentColor;
}
currentColor = pixelColor*(1.-color.a) + color.a*color;
currentZIndex = zIndex;
flipCount = 1;
}
}
}
}
if((flipCount & 0x01) != 0)
{
pixelColor = currentColor;
}
fragColor = pixelColor;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,290 +1,290 @@
/************************************************************//**
*
* @file: graphics_internal.h
* @author: Martin Fouilleul
* @date: 23/01/2023
* @revision:
*
*****************************************************************/
#ifndef __GRAPHICS_INTERNAL_H_
#define __GRAPHICS_INTERNAL_H_
#include"graphics.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum { MG_BACKEND_DUMMY,
MG_BACKEND_METAL,
MG_BACKEND_GL,
MG_BACKEND_GLES,
//...
} mg_backend_id;
typedef struct mg_surface_data mg_surface_data;
typedef void (*mg_surface_destroy_proc)(mg_surface_data* surface);
typedef void (*mg_surface_prepare_proc)(mg_surface_data* surface);
typedef void (*mg_surface_present_proc)(mg_surface_data* surface);
typedef mp_rect (*mg_surface_get_frame_proc)(mg_surface_data* surface);
typedef void (*mg_surface_set_frame_proc)(mg_surface_data* surface, mp_rect frame);
typedef bool (*mg_surface_get_hidden_proc)(mg_surface_data* surface);
typedef void (*mg_surface_set_hidden_proc)(mg_surface_data* surface, bool hidden);
typedef struct mg_surface_data
{
mg_backend_id backend;
mg_surface_destroy_proc destroy;
mg_surface_prepare_proc prepare;
mg_surface_present_proc present;
mg_surface_get_frame_proc getFrame;
mg_surface_set_frame_proc setFrame;
mg_surface_get_hidden_proc getHidden;
mg_surface_set_hidden_proc setHidden;
} mg_surface_data;
mg_surface mg_surface_alloc_handle(mg_surface_data* surface);
mg_surface_data* mg_surface_data_from_handle(mg_surface handle);
//---------------------------------------------------------------
// graphics structs
//---------------------------------------------------------------
typedef enum { MG_PATH_MOVE,
MG_PATH_LINE,
MG_PATH_QUADRATIC,
MG_PATH_CUBIC } mg_path_elt_type;
typedef struct mg_path_elt
{
mg_path_elt_type type;
vec2 p[3];
} mg_path_elt;
typedef struct mg_path_descriptor
{
u32 startIndex;
u32 count;
vec2 startPoint;
} mg_path_descriptor;
typedef struct mg_attributes
{
f32 width;
f32 tolerance;
mg_color color;
mg_joint_type joint;
f32 maxJointExcursion;
mg_cap_type cap;
mg_font font;
f32 fontSize;
mg_image image;
mp_rect clip;
} mg_attributes;
typedef struct mg_rounded_rect
{
f32 x;
f32 y;
f32 w;
f32 h;
f32 r;
} mg_rounded_rect;
typedef enum { MG_CMD_CLEAR = 0,
MG_CMD_FILL,
MG_CMD_STROKE,
MG_CMD_RECT_FILL,
MG_CMD_RECT_STROKE,
MG_CMD_ROUND_RECT_FILL,
MG_CMD_ROUND_RECT_STROKE,
MG_CMD_ELLIPSE_FILL,
MG_CMD_ELLIPSE_STROKE,
MG_CMD_JUMP,
MG_CMD_MATRIX_PUSH,
MG_CMD_MATRIX_POP,
MG_CMD_CLIP_PUSH,
MG_CMD_CLIP_POP,
MG_CMD_IMAGE_DRAW,
MG_CMD_ROUNDED_IMAGE_DRAW,
} mg_primitive_cmd;
typedef struct mg_primitive
{
mg_primitive_cmd cmd;
mg_attributes attributes;
union
{
mg_path_descriptor path;
mp_rect rect;
mg_rounded_rect roundedRect;
utf32 codePoint;
u32 jump;
mg_mat2x3 matrix;
};
} mg_primitive;
typedef struct mg_glyph_map_entry
{
unicode_range range;
u32 firstGlyphIndex;
} mg_glyph_map_entry;
typedef struct mg_glyph_data
{
bool exists;
utf32 codePoint;
mg_path_descriptor pathDescriptor;
mg_text_extents extents;
//...
} mg_glyph_data;
enum
{
MG_STREAM_MAX_COUNT = 128,
MG_IMAGE_MAX_COUNT = 128
};
typedef struct mg_image_data
{
list_elt listElt;
u32 generation;
mp_rect rect;
} mg_image_data;
enum
{
MG_MATRIX_STACK_MAX_DEPTH = 64,
MG_CLIP_STACK_MAX_DEPTH = 64,
MG_MAX_PATH_ELEMENT_COUNT = 2<<20,
MG_MAX_PRIMITIVE_COUNT = 8<<10
};
typedef struct mg_font_data
{
list_elt freeListElt;
u32 generation;
u32 rangeCount;
u32 glyphCount;
u32 outlineCount;
mg_glyph_map_entry* glyphMap;
mg_glyph_data* glyphs;
mg_path_elt* outlines;
f32 unitsPerEm;
mg_font_extents extents;
} mg_font_data;
typedef struct mg_vertex_layout
{
u32 maxVertexCount;
u32 maxIndexCount;
void* posBuffer;
u32 posStride;
void* cubicBuffer;
u32 cubicStride;
void* uvBuffer;
u32 uvStride;
void* colorBuffer;
u32 colorStride;
void* zIndexBuffer;
u32 zIndexStride;
void* clipsBuffer;
u32 clipsStride;
void* indexBuffer;
u32 indexStride;
} mg_vertex_layout;
typedef struct mg_canvas_backend mg_canvas_backend;
typedef void (*mg_canvas_backend_destroy_proc)(mg_canvas_backend* backend);
typedef void (*mg_canvas_backend_draw_buffers_proc)(mg_canvas_backend* backend, u32 vertexCount, u32 indexCount, mg_color clearColor);
typedef void (*mg_canvas_backend_atlas_upload_proc)(mg_canvas_backend* backend, mp_rect rect, u8* bytes);
typedef struct mg_canvas_backend
{
mg_vertex_layout vertexLayout;
mg_canvas_backend_destroy_proc destroy;
mg_canvas_backend_draw_buffers_proc drawBuffers;
mg_canvas_backend_atlas_upload_proc atlasUpload;
} mg_canvas_backend;
typedef struct mg_canvas_data
{
list_elt freeListElt;
u32 generation;
u64 frameCounter;
mg_mat2x3 transform;
mp_rect clip;
mg_attributes attributes;
bool textFlip;
mg_path_elt pathElements[MG_MAX_PATH_ELEMENT_COUNT];
mg_path_descriptor path;
vec2 subPathStartPoint;
vec2 subPathLastPoint;
mg_mat2x3 matrixStack[MG_MATRIX_STACK_MAX_DEPTH];
u32 matrixStackSize;
mp_rect clipStack[MG_CLIP_STACK_MAX_DEPTH];
u32 clipStackSize;
u32 nextZIndex;
u32 primitiveCount;
mg_primitive primitives[MG_MAX_PRIMITIVE_COUNT];
u32 vertexCount;
u32 indexCount;
mg_image_data images[MG_IMAGE_MAX_COUNT];
u32 imageNextIndex;
list_info imageFreeList;
vec2 atlasPos;
u32 atlasLineHeight;
mg_image blankImage;
mg_canvas_backend* backend;
} mg_canvas_data;
enum
{
MG_ATLAS_SIZE = 8192,
};
#ifdef __cplusplus
} // extern "C"
#endif
#endif //__GRAPHICS_INTERNAL_H_
/************************************************************//**
*
* @file: graphics_internal.h
* @author: Martin Fouilleul
* @date: 23/01/2023
* @revision:
*
*****************************************************************/
#ifndef __GRAPHICS_INTERNAL_H_
#define __GRAPHICS_INTERNAL_H_
#include"graphics.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum { MG_BACKEND_DUMMY,
MG_BACKEND_METAL,
MG_BACKEND_GL,
MG_BACKEND_GLES,
//...
} mg_backend_id;
typedef struct mg_surface_data mg_surface_data;
typedef void (*mg_surface_destroy_proc)(mg_surface_data* surface);
typedef void (*mg_surface_prepare_proc)(mg_surface_data* surface);
typedef void (*mg_surface_present_proc)(mg_surface_data* surface);
typedef mp_rect (*mg_surface_get_frame_proc)(mg_surface_data* surface);
typedef void (*mg_surface_set_frame_proc)(mg_surface_data* surface, mp_rect frame);
typedef bool (*mg_surface_get_hidden_proc)(mg_surface_data* surface);
typedef void (*mg_surface_set_hidden_proc)(mg_surface_data* surface, bool hidden);
typedef struct mg_surface_data
{
mg_backend_id backend;
mg_surface_destroy_proc destroy;
mg_surface_prepare_proc prepare;
mg_surface_present_proc present;
mg_surface_get_frame_proc getFrame;
mg_surface_set_frame_proc setFrame;
mg_surface_get_hidden_proc getHidden;
mg_surface_set_hidden_proc setHidden;
} mg_surface_data;
mg_surface mg_surface_alloc_handle(mg_surface_data* surface);
mg_surface_data* mg_surface_data_from_handle(mg_surface handle);
//---------------------------------------------------------------
// graphics structs
//---------------------------------------------------------------
typedef enum { MG_PATH_MOVE,
MG_PATH_LINE,
MG_PATH_QUADRATIC,
MG_PATH_CUBIC } mg_path_elt_type;
typedef struct mg_path_elt
{
mg_path_elt_type type;
vec2 p[3];
} mg_path_elt;
typedef struct mg_path_descriptor
{
u32 startIndex;
u32 count;
vec2 startPoint;
} mg_path_descriptor;
typedef struct mg_attributes
{
f32 width;
f32 tolerance;
mg_color color;
mg_joint_type joint;
f32 maxJointExcursion;
mg_cap_type cap;
mg_font font;
f32 fontSize;
mg_image image;
mp_rect clip;
} mg_attributes;
typedef struct mg_rounded_rect
{
f32 x;
f32 y;
f32 w;
f32 h;
f32 r;
} mg_rounded_rect;
typedef enum { MG_CMD_CLEAR = 0,
MG_CMD_FILL,
MG_CMD_STROKE,
MG_CMD_RECT_FILL,
MG_CMD_RECT_STROKE,
MG_CMD_ROUND_RECT_FILL,
MG_CMD_ROUND_RECT_STROKE,
MG_CMD_ELLIPSE_FILL,
MG_CMD_ELLIPSE_STROKE,
MG_CMD_JUMP,
MG_CMD_MATRIX_PUSH,
MG_CMD_MATRIX_POP,
MG_CMD_CLIP_PUSH,
MG_CMD_CLIP_POP,
MG_CMD_IMAGE_DRAW,
MG_CMD_ROUNDED_IMAGE_DRAW,
} mg_primitive_cmd;
typedef struct mg_primitive
{
mg_primitive_cmd cmd;
mg_attributes attributes;
union
{
mg_path_descriptor path;
mp_rect rect;
mg_rounded_rect roundedRect;
utf32 codePoint;
u32 jump;
mg_mat2x3 matrix;
};
} mg_primitive;
typedef struct mg_glyph_map_entry
{
unicode_range range;
u32 firstGlyphIndex;
} mg_glyph_map_entry;
typedef struct mg_glyph_data
{
bool exists;
utf32 codePoint;
mg_path_descriptor pathDescriptor;
mg_text_extents extents;
//...
} mg_glyph_data;
enum
{
MG_STREAM_MAX_COUNT = 128,
MG_IMAGE_MAX_COUNT = 128
};
typedef struct mg_image_data
{
list_elt listElt;
u32 generation;
mp_rect rect;
} mg_image_data;
enum
{
MG_MATRIX_STACK_MAX_DEPTH = 64,
MG_CLIP_STACK_MAX_DEPTH = 64,
MG_MAX_PATH_ELEMENT_COUNT = 2<<20,
MG_MAX_PRIMITIVE_COUNT = 8<<10
};
typedef struct mg_font_data
{
list_elt freeListElt;
u32 generation;
u32 rangeCount;
u32 glyphCount;
u32 outlineCount;
mg_glyph_map_entry* glyphMap;
mg_glyph_data* glyphs;
mg_path_elt* outlines;
f32 unitsPerEm;
mg_font_extents extents;
} mg_font_data;
typedef struct mg_vertex_layout
{
u32 maxVertexCount;
u32 maxIndexCount;
char* posBuffer;
u32 posStride;
char* cubicBuffer;
u32 cubicStride;
char* uvBuffer;
u32 uvStride;
char* colorBuffer;
u32 colorStride;
char* zIndexBuffer;
u32 zIndexStride;
char* clipBuffer;
u32 clipStride;
char* indexBuffer;
u32 indexStride;
} mg_vertex_layout;
typedef struct mg_canvas_backend mg_canvas_backend;
typedef void (*mg_canvas_backend_destroy_proc)(mg_canvas_backend* backend);
typedef void (*mg_canvas_backend_draw_buffers_proc)(mg_canvas_backend* backend, u32 vertexCount, u32 indexCount, mg_color clearColor);
typedef void (*mg_canvas_backend_atlas_upload_proc)(mg_canvas_backend* backend, mp_rect rect, u8* bytes);
typedef struct mg_canvas_backend
{
mg_vertex_layout vertexLayout;
mg_canvas_backend_destroy_proc destroy;
mg_canvas_backend_draw_buffers_proc drawBuffers;
mg_canvas_backend_atlas_upload_proc atlasUpload;
} mg_canvas_backend;
typedef struct mg_canvas_data
{
list_elt freeListElt;
u32 generation;
u64 frameCounter;
mg_mat2x3 transform;
mp_rect clip;
mg_attributes attributes;
bool textFlip;
mg_path_elt pathElements[MG_MAX_PATH_ELEMENT_COUNT];
mg_path_descriptor path;
vec2 subPathStartPoint;
vec2 subPathLastPoint;
mg_mat2x3 matrixStack[MG_MATRIX_STACK_MAX_DEPTH];
u32 matrixStackSize;
mp_rect clipStack[MG_CLIP_STACK_MAX_DEPTH];
u32 clipStackSize;
u32 nextZIndex;
u32 primitiveCount;
mg_primitive primitives[MG_MAX_PRIMITIVE_COUNT];
u32 vertexCount;
u32 indexCount;
mg_image_data images[MG_IMAGE_MAX_COUNT];
u32 imageNextIndex;
list_info imageFreeList;
vec2 atlasPos;
u32 atlasLineHeight;
mg_image blankImage;
mg_canvas_backend* backend;
} mg_canvas_data;
enum
{
MG_ATLAS_SIZE = 8192,
};
#ifdef __cplusplus
} // extern "C"
#endif
#endif //__GRAPHICS_INTERNAL_H_

View File

@ -1,442 +1,442 @@
/************************************************************//**
*
* @file: metal_canvas.m
* @author: Martin Fouilleul
* @date: 12/07/2020
* @revision: 24/01/2023
*
*****************************************************************/
#import<Metal/Metal.h>
#import<QuartzCore/CAMetalLayer.h>
#include<simd/simd.h>
#include"graphics_internal.h"
#include"macro_helpers.h"
#include"osx_app.h"
#include"metal_shader.h"
#define LOG_SUBSYSTEM "Graphics"
static const int MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH = 4<<20;
typedef struct mg_metal_canvas_backend
{
mg_canvas_backend interface;
mg_surface surface;
// permanent metal resources
id<MTLComputePipelineState> tilingPipeline;
id<MTLComputePipelineState> sortingPipeline;
id<MTLComputePipelineState> boxingPipeline;
id<MTLComputePipelineState> computePipeline;
id<MTLRenderPipelineState> renderPipeline;
mp_rect viewPort;
// textures and buffers
id<MTLTexture> outTexture;
id<MTLTexture> atlasTexture;
id<MTLBuffer> vertexBuffer;
id<MTLBuffer> indexBuffer;
id<MTLBuffer> tileCounters;
id<MTLBuffer> tilesArray;
id<MTLBuffer> triangleArray;
id<MTLBuffer> boxArray;
} mg_metal_canvas_backend;
mg_metal_surface* mg_metal_canvas_get_surface(mg_metal_canvas_backend* canvas)
{
mg_metal_surface* res = 0;
mg_surface_data* data = mg_surface_data_from_handle(canvas->surface);
if(data && data->backend == MG_BACKEND_METAL)
{
res = (mg_metal_surface*)data;
}
return(res);
}
void mg_metal_canvas_draw_buffers(mg_canvas_backend* interface, u32 vertexCount, u32 indexCount, mg_color clearColor)
{
mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface;
mg_metal_surface* surface = mg_metal_canvas_get_surface(backend);
if(!surface)
{
return;
}
@autoreleasepool
{
if(surface->commandBuffer == nil || surface->commandBuffer == nil)
{
mg_metal_surface_acquire_drawable_and_command_buffer(surface);
}
ASSERT(indexCount * sizeof(i32) < [backend->indexBuffer length]);
f32 scale = surface->metalLayer.contentsScale;
vector_uint2 viewportSize = {backend->viewPort.w * scale, backend->viewPort.h * scale};
//-----------------------------------------------------------
//NOTE(martin): encode the clear counter
//-----------------------------------------------------------
id<MTLBlitCommandEncoder> blitEncoder = [surface->commandBuffer blitCommandEncoder];
[blitEncoder fillBuffer: backend->tileCounters range: NSMakeRange(0, RENDERER_MAX_TILES*sizeof(uint)) value: 0];
[blitEncoder endEncoding];
//-----------------------------------------------------------
//NOTE(martin): encode the boxing pass
//-----------------------------------------------------------
id<MTLComputeCommandEncoder> boxEncoder = [surface->commandBuffer computeCommandEncoder];
[boxEncoder setComputePipelineState: backend->boxingPipeline];
[boxEncoder setBuffer: backend->vertexBuffer offset:0 atIndex: 0];
[boxEncoder setBuffer: backend->indexBuffer offset:0 atIndex: 1];
[boxEncoder setBuffer: backend->triangleArray offset:0 atIndex: 2];
[boxEncoder setBuffer: backend->boxArray offset:0 atIndex: 3];
[boxEncoder setBytes: &scale length: sizeof(float) atIndex: 4];
MTLSize boxGroupSize = MTLSizeMake(backend->boxingPipeline.maxTotalThreadsPerThreadgroup, 1, 1);
MTLSize boxGridSize = MTLSizeMake(indexCount/3, 1, 1);
[boxEncoder dispatchThreads: boxGridSize threadsPerThreadgroup: boxGroupSize];
[boxEncoder endEncoding];
//-----------------------------------------------------------
//NOTE(martin): encode the tiling pass
//-----------------------------------------------------------
id<MTLComputeCommandEncoder> tileEncoder = [surface->commandBuffer computeCommandEncoder];
[tileEncoder setComputePipelineState: backend->tilingPipeline];
[tileEncoder setBuffer: backend->boxArray offset:0 atIndex: 0];
[tileEncoder setBuffer: backend->tileCounters offset:0 atIndex: 1];
[tileEncoder setBuffer: backend->tilesArray offset:0 atIndex: 2];
[tileEncoder setBytes: &viewportSize length: sizeof(vector_uint2) atIndex: 3];
[tileEncoder dispatchThreads: boxGridSize threadsPerThreadgroup: boxGroupSize];
[tileEncoder endEncoding];
//-----------------------------------------------------------
//NOTE(martin): encode the sorting pass
//-----------------------------------------------------------
id<MTLComputeCommandEncoder> sortEncoder = [surface->commandBuffer computeCommandEncoder];
[sortEncoder setComputePipelineState: backend->sortingPipeline];
[sortEncoder setBuffer: backend->tileCounters offset:0 atIndex: 0];
[sortEncoder setBuffer: backend->triangleArray offset:0 atIndex: 1];
[sortEncoder setBuffer: backend->tilesArray offset:0 atIndex: 2];
[sortEncoder setBytes: &viewportSize length: sizeof(vector_uint2) atIndex: 3];
u32 nTilesX = (viewportSize.x + RENDERER_TILE_SIZE - 1)/RENDERER_TILE_SIZE;
u32 nTilesY = (viewportSize.y + RENDERER_TILE_SIZE - 1)/RENDERER_TILE_SIZE;
MTLSize sortGroupSize = MTLSizeMake(backend->boxingPipeline.maxTotalThreadsPerThreadgroup, 1, 1);
MTLSize sortGridSize = MTLSizeMake(nTilesX*nTilesY, 1, 1);
[sortEncoder dispatchThreads: sortGridSize threadsPerThreadgroup: sortGroupSize];
[sortEncoder endEncoding];
//-----------------------------------------------------------
//NOTE(martin): create compute encoder and encode commands
//-----------------------------------------------------------
vector_float4 clearColorVec4 = {clearColor.r, clearColor.g, clearColor.b, clearColor.a};
id<MTLComputeCommandEncoder> encoder = [surface->commandBuffer computeCommandEncoder];
[encoder setComputePipelineState:backend->computePipeline];
[encoder setTexture: backend->outTexture atIndex: 0];
[encoder setTexture: backend->atlasTexture atIndex: 1];
[encoder setBuffer: backend->vertexBuffer offset:0 atIndex: 0];
[encoder setBuffer: backend->tileCounters offset:0 atIndex: 1];
[encoder setBuffer: backend->tilesArray offset:0 atIndex: 2];
[encoder setBuffer: backend->triangleArray offset:0 atIndex: 3];
[encoder setBuffer: backend->boxArray offset:0 atIndex: 4];
[encoder setBytes: &clearColorVec4 length: sizeof(vector_float4) atIndex: 5];
//TODO: check that we don't exceed maxTotalThreadsPerThreadgroup
DEBUG_ASSERT(RENDERER_TILE_SIZE*RENDERER_TILE_SIZE <= backend->computePipeline.maxTotalThreadsPerThreadgroup);
MTLSize threadGridSize = MTLSizeMake(viewportSize.x, viewportSize.y, 1);
MTLSize threadGroupSize = MTLSizeMake(RENDERER_TILE_SIZE, RENDERER_TILE_SIZE, 1);
[encoder dispatchThreads: threadGridSize threadsPerThreadgroup:threadGroupSize];
[encoder endEncoding];
//-----------------------------------------------------------
//NOTE(martin): acquire drawable, create render encoder to blit texture to framebuffer
//-----------------------------------------------------------
MTLViewport viewport = {backend->viewPort.x * scale,
backend->viewPort.y * scale,
backend->viewPort.w * scale,
backend->viewPort.h * scale,
0,
1};
MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
renderPassDescriptor.colorAttachments[0].texture = surface->drawable.texture;
renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
id<MTLRenderCommandEncoder> renderEncoder = [surface->commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
[renderEncoder setViewport: viewport];
[renderEncoder setRenderPipelineState: backend->renderPipeline];
[renderEncoder setFragmentTexture: backend->outTexture atIndex: 0];
[renderEncoder drawPrimitives: MTLPrimitiveTypeTriangle
vertexStart: 0
vertexCount: 3 ];
[renderEncoder endEncoding];
}
}
/*
void mg_metal_canvas_viewport(mg_canvas_backend* interface, mp_rect viewPort)
{
mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface;
mg_metal_surface* surface = mg_metal_canvas_get_surface(backend);
if(!surface)
{
return;
}
backend->viewPort = viewPort;
@autoreleasepool
{
f32 scale = surface->metalLayer.contentsScale;
CGSize drawableSize = (CGSize){.width = viewPort.w * scale, .height = viewPort.h * scale};
[backend->outTexture release];
MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init];
texDesc.textureType = MTLTextureType2D;
texDesc.storageMode = MTLStorageModePrivate;
texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
texDesc.pixelFormat = MTLPixelFormatBGRA8Unorm;// MTLPixelFormatBGRA8Unorm_sRGB;
texDesc.width = drawableSize.width;
texDesc.height = drawableSize.height;
backend->outTexture = [surface->device newTextureWithDescriptor:texDesc];
}
}
*/
void mg_metal_canvas_update_vertex_layout(mg_metal_canvas_backend* backend)
{
char* vertexBase = (char*)[backend->vertexBuffer contents];
backend->interface.vertexLayout = (mg_vertex_layout){
.maxVertexCount = MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH,
.maxIndexCount = MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH,
.posBuffer = vertexBase + offsetof(mg_vertex, pos),
.posStride = sizeof(mg_vertex),
.cubicBuffer = vertexBase + offsetof(mg_vertex, cubic),
.cubicStride = sizeof(mg_vertex),
.uvBuffer = vertexBase + offsetof(mg_vertex, uv),
.uvStride = sizeof(mg_vertex),
.colorBuffer = vertexBase + offsetof(mg_vertex, color),
.colorStride = sizeof(mg_vertex),
.zIndexBuffer = vertexBase + offsetof(mg_vertex, zIndex),
.zIndexStride = sizeof(mg_vertex),
.clipsBuffer = vertexBase + offsetof(mg_vertex, clip),
.clipsStride = sizeof(mg_vertex),
.indexBuffer = [backend->indexBuffer contents],
.indexStride = sizeof(int)};
}
void mg_metal_canvas_destroy(mg_canvas_backend* interface)
{
mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface;
@autoreleasepool
{
[backend->outTexture release];
[backend->atlasTexture release];
[backend->vertexBuffer release];
[backend->indexBuffer release];
[backend->tilesArray release];
[backend->triangleArray release];
[backend->boxArray release];
[backend->computePipeline release];
}
}
void mg_metal_canvas_atlas_upload(mg_canvas_backend* interface, mp_rect rect, u8* bytes)
{@autoreleasepool{
mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface;
MTLRegion region = MTLRegionMake2D(rect.x, rect.y, rect.w, rect.h);
[backend->atlasTexture replaceRegion:region
mipmapLevel:0
withBytes:(void*)bytes
bytesPerRow: 4 * rect.w];
}}
mg_canvas_backend* mg_metal_canvas_create(mg_surface surface)
{
mg_metal_canvas_backend* backend = 0;
mg_surface_data* surfaceData = mg_surface_data_from_handle(surface);
if(surfaceData && surfaceData->backend == MG_BACKEND_METAL)
{
mg_metal_surface* metalSurface = (mg_metal_surface*)surfaceData;
backend = malloc_type(mg_metal_canvas_backend);
backend->surface = surface;
//NOTE(martin): setup interface functions
backend->interface.destroy = mg_metal_canvas_destroy;
backend->interface.drawBuffers = mg_metal_canvas_draw_buffers;
backend->interface.atlasUpload = mg_metal_canvas_atlas_upload;
mp_rect frame = mg_surface_get_frame(surface);
backend->viewPort = (mp_rect){0, 0, frame.w, frame.h};
@autoreleasepool
{
f32 scale = metalSurface->metalLayer.contentsScale;
CGSize drawableSize = (CGSize){.width = backend->viewPort.w * scale, .height = backend->viewPort.h * scale};
//-----------------------------------------------------------
//NOTE(martin): create our output texture
//-----------------------------------------------------------
MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init];
texDesc.textureType = MTLTextureType2D;
texDesc.storageMode = MTLStorageModePrivate;
texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
texDesc.pixelFormat = MTLPixelFormatBGRA8Unorm;// MTLPixelFormatBGRA8Unorm_sRGB;
texDesc.width = drawableSize.width;
texDesc.height = drawableSize.height;
backend->outTexture = [metalSurface->device newTextureWithDescriptor:texDesc];
//TODO(martin): retain ?
//-----------------------------------------------------------
//NOTE(martin): create our atlas texture
//-----------------------------------------------------------
texDesc.textureType = MTLTextureType2D;
texDesc.storageMode = MTLStorageModeManaged;
texDesc.usage = MTLTextureUsageShaderRead;
texDesc.pixelFormat = MTLPixelFormatRGBA8Unorm; //MTLPixelFormatBGRA8Unorm;
texDesc.width = MG_ATLAS_SIZE;
texDesc.height = MG_ATLAS_SIZE;
backend->atlasTexture = [metalSurface->device newTextureWithDescriptor:texDesc];
//-----------------------------------------------------------
//NOTE(martin): create buffers for vertex and index
//-----------------------------------------------------------
MTLResourceOptions bufferOptions = MTLResourceCPUCacheModeWriteCombined
| MTLResourceStorageModeShared;
backend->indexBuffer = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(int)
options: bufferOptions];
backend->vertexBuffer = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(mg_vertex)
options: bufferOptions];
backend->tilesArray = [metalSurface->device newBufferWithLength: RENDERER_TILE_BUFFER_SIZE*sizeof(int)*RENDERER_MAX_TILES
options: MTLResourceStorageModePrivate];
backend->triangleArray = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(mg_triangle_data)
options: MTLResourceStorageModePrivate];
backend->boxArray = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(vector_float4)
options: MTLResourceStorageModePrivate];
//TODO(martin): retain ?
//-----------------------------------------------------------
//NOTE(martin): create and initialize tile counters
//-----------------------------------------------------------
backend->tileCounters = [metalSurface->device newBufferWithLength: RENDERER_MAX_TILES*sizeof(uint)
options: MTLResourceStorageModePrivate];
id<MTLCommandBuffer> commandBuffer = [metalSurface->commandQueue commandBuffer];
id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];
[blitEncoder fillBuffer: backend->tileCounters range: NSMakeRange(0, RENDERER_MAX_TILES*sizeof(uint)) value: 0];
[blitEncoder endEncoding];
[commandBuffer commit];
//-----------------------------------------------------------
//NOTE(martin): load the library
//-----------------------------------------------------------
//TODO(martin): filepath magic to find metallib path when not in the working directory
str8 shaderPath = mp_app_get_resource_path(mem_scratch(), "../resources/metal_shader.metallib");
NSString* metalFileName = [[NSString alloc] initWithBytes: shaderPath.ptr length:shaderPath.len encoding: NSUTF8StringEncoding];
NSError* err = 0;
id<MTLLibrary> library = [metalSurface->device newLibraryWithFile: metalFileName error:&err];
if(err != nil)
{
const char* errStr = [[err localizedDescription] UTF8String];
LOG_ERROR("error : %s\n", errStr);
return(0);
}
id<MTLFunction> tilingFunction = [library newFunctionWithName:@"TileKernel"];
id<MTLFunction> sortingFunction = [library newFunctionWithName:@"SortKernel"];
id<MTLFunction> boxingFunction = [library newFunctionWithName:@"BoundingBoxKernel"];
id<MTLFunction> computeFunction = [library newFunctionWithName:@"RenderKernel"];
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"VertexShader"];
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"FragmentShader"];
//-----------------------------------------------------------
//NOTE(martin): setup our data layout and pipeline state
//-----------------------------------------------------------
NSError* error = NULL;
backend->computePipeline = [metalSurface->device newComputePipelineStateWithFunction: computeFunction
error:&error];
ASSERT(backend->computePipeline);
MTLComputePipelineDescriptor* tilingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init];
tilingPipelineDesc.computeFunction = tilingFunction;
// tilingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
backend->tilingPipeline = [metalSurface->device newComputePipelineStateWithDescriptor: tilingPipelineDesc
options: MTLPipelineOptionNone
reflection: nil
error: &error];
MTLComputePipelineDescriptor* sortingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init];
sortingPipelineDesc.computeFunction = sortingFunction;
// sortingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
backend->sortingPipeline = [metalSurface->device newComputePipelineStateWithDescriptor: sortingPipelineDesc
options: MTLPipelineOptionNone
reflection: nil
error: &error];
MTLComputePipelineDescriptor* boxingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init];
boxingPipelineDesc.computeFunction = boxingFunction;
// boxingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
backend->boxingPipeline = [metalSurface->device newComputePipelineStateWithDescriptor: boxingPipelineDesc
options: MTLPipelineOptionNone
reflection: nil
error: &error];
//-----------------------------------------------------------
//NOTE(martin): setup our render pipeline state
//-----------------------------------------------------------
// create and initialize the pipeline state descriptor
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"My simple pipeline";
pipelineStateDescriptor.vertexFunction = vertexFunction;
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = metalSurface->metalLayer.pixelFormat;
// create render pipeline
backend->renderPipeline = [metalSurface->device newRenderPipelineStateWithDescriptor: pipelineStateDescriptor error:&err];
if(err != nil)
{
const char* errStr = [[err localizedDescription] UTF8String];
const char* descStr = [[err localizedFailureReason] UTF8String];
const char* recovStr = [[err localizedRecoverySuggestion] UTF8String];
LOG_ERROR("(%li) %s. %s. %s\n", [err code], errStr, descStr, recovStr);
return(0);
}
}
mg_metal_canvas_update_vertex_layout(backend);
}
return((mg_canvas_backend*)backend);
}
#undef LOG_SUBSYSTEM
/************************************************************//**
*
* @file: metal_canvas.m
* @author: Martin Fouilleul
* @date: 12/07/2020
* @revision: 24/01/2023
*
*****************************************************************/
#import<Metal/Metal.h>
#import<QuartzCore/CAMetalLayer.h>
#include<simd/simd.h>
#include"graphics_internal.h"
#include"macro_helpers.h"
#include"osx_app.h"
#include"metal_shader.h"
#define LOG_SUBSYSTEM "Graphics"
static const int MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH = 4<<20;
typedef struct mg_metal_canvas_backend
{
mg_canvas_backend interface;
mg_surface surface;
// permanent metal resources
id<MTLComputePipelineState> tilingPipeline;
id<MTLComputePipelineState> sortingPipeline;
id<MTLComputePipelineState> boxingPipeline;
id<MTLComputePipelineState> computePipeline;
id<MTLRenderPipelineState> renderPipeline;
mp_rect viewPort;
// textures and buffers
id<MTLTexture> outTexture;
id<MTLTexture> atlasTexture;
id<MTLBuffer> vertexBuffer;
id<MTLBuffer> indexBuffer;
id<MTLBuffer> tileCounters;
id<MTLBuffer> tilesArray;
id<MTLBuffer> triangleArray;
id<MTLBuffer> boxArray;
} mg_metal_canvas_backend;
mg_metal_surface* mg_metal_canvas_get_surface(mg_metal_canvas_backend* canvas)
{
mg_metal_surface* res = 0;
mg_surface_data* data = mg_surface_data_from_handle(canvas->surface);
if(data && data->backend == MG_BACKEND_METAL)
{
res = (mg_metal_surface*)data;
}
return(res);
}
void mg_metal_canvas_draw_buffers(mg_canvas_backend* interface, u32 vertexCount, u32 indexCount, mg_color clearColor)
{
mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface;
mg_metal_surface* surface = mg_metal_canvas_get_surface(backend);
if(!surface)
{
return;
}
@autoreleasepool
{
if(surface->commandBuffer == nil || surface->commandBuffer == nil)
{
mg_metal_surface_acquire_drawable_and_command_buffer(surface);
}
ASSERT(indexCount * sizeof(i32) < [backend->indexBuffer length]);
f32 scale = surface->metalLayer.contentsScale;
vector_uint2 viewportSize = {backend->viewPort.w * scale, backend->viewPort.h * scale};
//-----------------------------------------------------------
//NOTE(martin): encode the clear counter
//-----------------------------------------------------------
id<MTLBlitCommandEncoder> blitEncoder = [surface->commandBuffer blitCommandEncoder];
[blitEncoder fillBuffer: backend->tileCounters range: NSMakeRange(0, RENDERER_MAX_TILES*sizeof(uint)) value: 0];
[blitEncoder endEncoding];
//-----------------------------------------------------------
//NOTE(martin): encode the boxing pass
//-----------------------------------------------------------
id<MTLComputeCommandEncoder> boxEncoder = [surface->commandBuffer computeCommandEncoder];
[boxEncoder setComputePipelineState: backend->boxingPipeline];
[boxEncoder setBuffer: backend->vertexBuffer offset:0 atIndex: 0];
[boxEncoder setBuffer: backend->indexBuffer offset:0 atIndex: 1];
[boxEncoder setBuffer: backend->triangleArray offset:0 atIndex: 2];
[boxEncoder setBuffer: backend->boxArray offset:0 atIndex: 3];
[boxEncoder setBytes: &scale length: sizeof(float) atIndex: 4];
MTLSize boxGroupSize = MTLSizeMake(backend->boxingPipeline.maxTotalThreadsPerThreadgroup, 1, 1);
MTLSize boxGridSize = MTLSizeMake(indexCount/3, 1, 1);
[boxEncoder dispatchThreads: boxGridSize threadsPerThreadgroup: boxGroupSize];
[boxEncoder endEncoding];
//-----------------------------------------------------------
//NOTE(martin): encode the tiling pass
//-----------------------------------------------------------
id<MTLComputeCommandEncoder> tileEncoder = [surface->commandBuffer computeCommandEncoder];
[tileEncoder setComputePipelineState: backend->tilingPipeline];
[tileEncoder setBuffer: backend->boxArray offset:0 atIndex: 0];
[tileEncoder setBuffer: backend->tileCounters offset:0 atIndex: 1];
[tileEncoder setBuffer: backend->tilesArray offset:0 atIndex: 2];
[tileEncoder setBytes: &viewportSize length: sizeof(vector_uint2) atIndex: 3];
[tileEncoder dispatchThreads: boxGridSize threadsPerThreadgroup: boxGroupSize];
[tileEncoder endEncoding];
//-----------------------------------------------------------
//NOTE(martin): encode the sorting pass
//-----------------------------------------------------------
id<MTLComputeCommandEncoder> sortEncoder = [surface->commandBuffer computeCommandEncoder];
[sortEncoder setComputePipelineState: backend->sortingPipeline];
[sortEncoder setBuffer: backend->tileCounters offset:0 atIndex: 0];
[sortEncoder setBuffer: backend->triangleArray offset:0 atIndex: 1];
[sortEncoder setBuffer: backend->tilesArray offset:0 atIndex: 2];
[sortEncoder setBytes: &viewportSize length: sizeof(vector_uint2) atIndex: 3];
u32 nTilesX = (viewportSize.x + RENDERER_TILE_SIZE - 1)/RENDERER_TILE_SIZE;
u32 nTilesY = (viewportSize.y + RENDERER_TILE_SIZE - 1)/RENDERER_TILE_SIZE;
MTLSize sortGroupSize = MTLSizeMake(backend->boxingPipeline.maxTotalThreadsPerThreadgroup, 1, 1);
MTLSize sortGridSize = MTLSizeMake(nTilesX*nTilesY, 1, 1);
[sortEncoder dispatchThreads: sortGridSize threadsPerThreadgroup: sortGroupSize];
[sortEncoder endEncoding];
//-----------------------------------------------------------
//NOTE(martin): create compute encoder and encode commands
//-----------------------------------------------------------
vector_float4 clearColorVec4 = {clearColor.r, clearColor.g, clearColor.b, clearColor.a};
id<MTLComputeCommandEncoder> encoder = [surface->commandBuffer computeCommandEncoder];
[encoder setComputePipelineState:backend->computePipeline];
[encoder setTexture: backend->outTexture atIndex: 0];
[encoder setTexture: backend->atlasTexture atIndex: 1];
[encoder setBuffer: backend->vertexBuffer offset:0 atIndex: 0];
[encoder setBuffer: backend->tileCounters offset:0 atIndex: 1];
[encoder setBuffer: backend->tilesArray offset:0 atIndex: 2];
[encoder setBuffer: backend->triangleArray offset:0 atIndex: 3];
[encoder setBuffer: backend->boxArray offset:0 atIndex: 4];
[encoder setBytes: &clearColorVec4 length: sizeof(vector_float4) atIndex: 5];
//TODO: check that we don't exceed maxTotalThreadsPerThreadgroup
DEBUG_ASSERT(RENDERER_TILE_SIZE*RENDERER_TILE_SIZE <= backend->computePipeline.maxTotalThreadsPerThreadgroup);
MTLSize threadGridSize = MTLSizeMake(viewportSize.x, viewportSize.y, 1);
MTLSize threadGroupSize = MTLSizeMake(RENDERER_TILE_SIZE, RENDERER_TILE_SIZE, 1);
[encoder dispatchThreads: threadGridSize threadsPerThreadgroup:threadGroupSize];
[encoder endEncoding];
//-----------------------------------------------------------
//NOTE(martin): acquire drawable, create render encoder to blit texture to framebuffer
//-----------------------------------------------------------
MTLViewport viewport = {backend->viewPort.x * scale,
backend->viewPort.y * scale,
backend->viewPort.w * scale,
backend->viewPort.h * scale,
0,
1};
MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
renderPassDescriptor.colorAttachments[0].texture = surface->drawable.texture;
renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
id<MTLRenderCommandEncoder> renderEncoder = [surface->commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
[renderEncoder setViewport: viewport];
[renderEncoder setRenderPipelineState: backend->renderPipeline];
[renderEncoder setFragmentTexture: backend->outTexture atIndex: 0];
[renderEncoder drawPrimitives: MTLPrimitiveTypeTriangle
vertexStart: 0
vertexCount: 3 ];
[renderEncoder endEncoding];
}
}
/*
void mg_metal_canvas_viewport(mg_canvas_backend* interface, mp_rect viewPort)
{
mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface;
mg_metal_surface* surface = mg_metal_canvas_get_surface(backend);
if(!surface)
{
return;
}
backend->viewPort = viewPort;
@autoreleasepool
{
f32 scale = surface->metalLayer.contentsScale;
CGSize drawableSize = (CGSize){.width = viewPort.w * scale, .height = viewPort.h * scale};
[backend->outTexture release];
MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init];
texDesc.textureType = MTLTextureType2D;
texDesc.storageMode = MTLStorageModePrivate;
texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
texDesc.pixelFormat = MTLPixelFormatBGRA8Unorm;// MTLPixelFormatBGRA8Unorm_sRGB;
texDesc.width = drawableSize.width;
texDesc.height = drawableSize.height;
backend->outTexture = [surface->device newTextureWithDescriptor:texDesc];
}
}
*/
void mg_metal_canvas_update_vertex_layout(mg_metal_canvas_backend* backend)
{
char* vertexBase = (char*)[backend->vertexBuffer contents];
backend->interface.vertexLayout = (mg_vertex_layout){
.maxVertexCount = MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH,
.maxIndexCount = MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH,
.posBuffer = vertexBase + offsetof(mg_vertex, pos),
.posStride = sizeof(mg_vertex),
.cubicBuffer = vertexBase + offsetof(mg_vertex, cubic),
.cubicStride = sizeof(mg_vertex),
.uvBuffer = vertexBase + offsetof(mg_vertex, uv),
.uvStride = sizeof(mg_vertex),
.colorBuffer = vertexBase + offsetof(mg_vertex, color),
.colorStride = sizeof(mg_vertex),
.zIndexBuffer = vertexBase + offsetof(mg_vertex, zIndex),
.zIndexStride = sizeof(mg_vertex),
.clipBuffer = vertexBase + offsetof(mg_vertex, clip),
.clipStride = sizeof(mg_vertex),
.indexBuffer = [backend->indexBuffer contents],
.indexStride = sizeof(int)};
}
void mg_metal_canvas_destroy(mg_canvas_backend* interface)
{
mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface;
@autoreleasepool
{
[backend->outTexture release];
[backend->atlasTexture release];
[backend->vertexBuffer release];
[backend->indexBuffer release];
[backend->tilesArray release];
[backend->triangleArray release];
[backend->boxArray release];
[backend->computePipeline release];
}
}
void mg_metal_canvas_atlas_upload(mg_canvas_backend* interface, mp_rect rect, u8* bytes)
{@autoreleasepool{
mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface;
MTLRegion region = MTLRegionMake2D(rect.x, rect.y, rect.w, rect.h);
[backend->atlasTexture replaceRegion:region
mipmapLevel:0
withBytes:(void*)bytes
bytesPerRow: 4 * rect.w];
}}
mg_canvas_backend* mg_metal_canvas_create(mg_surface surface)
{
mg_metal_canvas_backend* backend = 0;
mg_surface_data* surfaceData = mg_surface_data_from_handle(surface);
if(surfaceData && surfaceData->backend == MG_BACKEND_METAL)
{
mg_metal_surface* metalSurface = (mg_metal_surface*)surfaceData;
backend = malloc_type(mg_metal_canvas_backend);
backend->surface = surface;
//NOTE(martin): setup interface functions
backend->interface.destroy = mg_metal_canvas_destroy;
backend->interface.drawBuffers = mg_metal_canvas_draw_buffers;
backend->interface.atlasUpload = mg_metal_canvas_atlas_upload;
mp_rect frame = mg_surface_get_frame(surface);
backend->viewPort = (mp_rect){0, 0, frame.w, frame.h};
@autoreleasepool
{
f32 scale = metalSurface->metalLayer.contentsScale;
CGSize drawableSize = (CGSize){.width = backend->viewPort.w * scale, .height = backend->viewPort.h * scale};
//-----------------------------------------------------------
//NOTE(martin): create our output texture
//-----------------------------------------------------------
MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init];
texDesc.textureType = MTLTextureType2D;
texDesc.storageMode = MTLStorageModePrivate;
texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
texDesc.pixelFormat = MTLPixelFormatBGRA8Unorm;// MTLPixelFormatBGRA8Unorm_sRGB;
texDesc.width = drawableSize.width;
texDesc.height = drawableSize.height;
backend->outTexture = [metalSurface->device newTextureWithDescriptor:texDesc];
//TODO(martin): retain ?
//-----------------------------------------------------------
//NOTE(martin): create our atlas texture
//-----------------------------------------------------------
texDesc.textureType = MTLTextureType2D;
texDesc.storageMode = MTLStorageModeManaged;
texDesc.usage = MTLTextureUsageShaderRead;
texDesc.pixelFormat = MTLPixelFormatRGBA8Unorm; //MTLPixelFormatBGRA8Unorm;
texDesc.width = MG_ATLAS_SIZE;
texDesc.height = MG_ATLAS_SIZE;
backend->atlasTexture = [metalSurface->device newTextureWithDescriptor:texDesc];
//-----------------------------------------------------------
//NOTE(martin): create buffers for vertex and index
//-----------------------------------------------------------
MTLResourceOptions bufferOptions = MTLResourceCPUCacheModeWriteCombined
| MTLResourceStorageModeShared;
backend->indexBuffer = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(int)
options: bufferOptions];
backend->vertexBuffer = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(mg_vertex)
options: bufferOptions];
backend->tilesArray = [metalSurface->device newBufferWithLength: RENDERER_TILE_BUFFER_SIZE*sizeof(int)*RENDERER_MAX_TILES
options: MTLResourceStorageModePrivate];
backend->triangleArray = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(mg_triangle_data)
options: MTLResourceStorageModePrivate];
backend->boxArray = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(vector_float4)
options: MTLResourceStorageModePrivate];
//TODO(martin): retain ?
//-----------------------------------------------------------
//NOTE(martin): create and initialize tile counters
//-----------------------------------------------------------
backend->tileCounters = [metalSurface->device newBufferWithLength: RENDERER_MAX_TILES*sizeof(uint)
options: MTLResourceStorageModePrivate];
id<MTLCommandBuffer> commandBuffer = [metalSurface->commandQueue commandBuffer];
id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];
[blitEncoder fillBuffer: backend->tileCounters range: NSMakeRange(0, RENDERER_MAX_TILES*sizeof(uint)) value: 0];
[blitEncoder endEncoding];
[commandBuffer commit];
//-----------------------------------------------------------
//NOTE(martin): load the library
//-----------------------------------------------------------
//TODO(martin): filepath magic to find metallib path when not in the working directory
str8 shaderPath = mp_app_get_resource_path(mem_scratch(), "../resources/metal_shader.metallib");
NSString* metalFileName = [[NSString alloc] initWithBytes: shaderPath.ptr length:shaderPath.len encoding: NSUTF8StringEncoding];
NSError* err = 0;
id<MTLLibrary> library = [metalSurface->device newLibraryWithFile: metalFileName error:&err];
if(err != nil)
{
const char* errStr = [[err localizedDescription] UTF8String];
LOG_ERROR("error : %s\n", errStr);
return(0);
}
id<MTLFunction> tilingFunction = [library newFunctionWithName:@"TileKernel"];
id<MTLFunction> sortingFunction = [library newFunctionWithName:@"SortKernel"];
id<MTLFunction> boxingFunction = [library newFunctionWithName:@"BoundingBoxKernel"];
id<MTLFunction> computeFunction = [library newFunctionWithName:@"RenderKernel"];
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"VertexShader"];
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"FragmentShader"];
//-----------------------------------------------------------
//NOTE(martin): setup our data layout and pipeline state
//-----------------------------------------------------------
NSError* error = NULL;
backend->computePipeline = [metalSurface->device newComputePipelineStateWithFunction: computeFunction
error:&error];
ASSERT(backend->computePipeline);
MTLComputePipelineDescriptor* tilingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init];
tilingPipelineDesc.computeFunction = tilingFunction;
// tilingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
backend->tilingPipeline = [metalSurface->device newComputePipelineStateWithDescriptor: tilingPipelineDesc
options: MTLPipelineOptionNone
reflection: nil
error: &error];
MTLComputePipelineDescriptor* sortingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init];
sortingPipelineDesc.computeFunction = sortingFunction;
// sortingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
backend->sortingPipeline = [metalSurface->device newComputePipelineStateWithDescriptor: sortingPipelineDesc
options: MTLPipelineOptionNone
reflection: nil
error: &error];
MTLComputePipelineDescriptor* boxingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init];
boxingPipelineDesc.computeFunction = boxingFunction;
// boxingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
backend->boxingPipeline = [metalSurface->device newComputePipelineStateWithDescriptor: boxingPipelineDesc
options: MTLPipelineOptionNone
reflection: nil
error: &error];
//-----------------------------------------------------------
//NOTE(martin): setup our render pipeline state
//-----------------------------------------------------------
// create and initialize the pipeline state descriptor
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"My simple pipeline";
pipelineStateDescriptor.vertexFunction = vertexFunction;
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = metalSurface->metalLayer.pixelFormat;
// create render pipeline
backend->renderPipeline = [metalSurface->device newRenderPipelineStateWithDescriptor: pipelineStateDescriptor error:&err];
if(err != nil)
{
const char* errStr = [[err localizedDescription] UTF8String];
const char* descStr = [[err localizedFailureReason] UTF8String];
const char* recovStr = [[err localizedRecoverySuggestion] UTF8String];
LOG_ERROR("(%li) %s. %s. %s\n", [err code], errStr, descStr, recovStr);
return(0);
}
}
mg_metal_canvas_update_vertex_layout(backend);
}
return((mg_canvas_backend*)backend);
}
#undef LOG_SUBSYSTEM

View File

@ -1,349 +1,349 @@
#include<metal_stdlib>
#include<simd/simd.h>
#include"metal_shader.h"
using namespace metal;
struct vs_out
{
float4 pos [[position]];
float2 uv;
};
vertex vs_out VertexShader(ushort vid [[vertex_id]])
{
vs_out out;
out.uv = float2((vid << 1) & 2, vid & 2);
out.pos = float4(out.uv * float2(2, 2) + float2(-1, -1), 0, 1);
return(out);
}
fragment float4 FragmentShader(vs_out i [[stage_in]], texture2d<float> tex [[texture(0)]])
{
constexpr sampler smp(mip_filter::nearest, mag_filter::linear, min_filter::linear);
return(float4(tex.sample(smp, i.uv).rgb, 1));
}
bool is_top_left(float2 a, float2 b)
{
return( (a.y == b.y && b.x < a.x)
||(b.y < a.y));
}
kernel void BoundingBoxKernel(constant mg_vertex* vertexBuffer [[buffer(0)]],
constant uint* indexBuffer [[buffer(1)]],
device mg_triangle_data* triangleArray [[buffer(2)]],
device float4* boxArray [[buffer(3)]],
constant float* contentsScaling [[buffer(4)]],
uint gid [[thread_position_in_grid]])
{
uint triangleIndex = gid;
uint vertexIndex = triangleIndex*3;
uint i0 = indexBuffer[vertexIndex];
uint i1 = indexBuffer[vertexIndex+1];
uint i2 = indexBuffer[vertexIndex+2];
float2 p0 = vertexBuffer[i0].pos * contentsScaling[0];
float2 p1 = vertexBuffer[i1].pos * contentsScaling[0];
float2 p2 = vertexBuffer[i2].pos * contentsScaling[0];
//NOTE(martin): compute triangle bounding box
float2 boxMin = min(min(p0, p1), p2);
float2 boxMax = max(max(p0, p1), p2);
//NOTE(martin): clip bounding box against clip rect
vector_float4 clip = contentsScaling[0]*vertexBuffer[indexBuffer[vertexIndex]].clip;
float2 clipMin(clip.x, clip.y);
float2 clipMax(clip.x + clip.z-1, clip.y + clip.w-1);
//NOTE(martin): intersect with current clip
boxMin = max(boxMin, clipMin);
boxMax = min(boxMax, clipMax);
//NOTE(martin): reorder triangle counter-clockwise and compute bias for each edge
float cw = (p1 - p0).x*(p2 - p0).y - (p1 - p0).y*(p2 - p0).x;
if(cw < 0)
{
uint tmpIndex = i1;
i1 = i2;
i2 = tmpIndex;
float2 tmpPoint = p1;
p1 = p2;
p2 = tmpPoint;
}
int bias0 = is_top_left(p1, p2) ? 0 : -1;
int bias1 = is_top_left(p2, p0) ? 0 : -1;
int bias2 = is_top_left(p0, p1) ? 0 : -1;
//NOTE(martin): fill triangle data
boxArray[triangleIndex] = float4(boxMin.x, boxMin.y, boxMax.x, boxMax.y);
triangleArray[triangleIndex].zIndex = vertexBuffer[i0].zIndex;
triangleArray[triangleIndex].i0 = i0;
triangleArray[triangleIndex].i1 = i1;
triangleArray[triangleIndex].i2 = i2;
triangleArray[triangleIndex].p0 = p0;
triangleArray[triangleIndex].p1 = p1;
triangleArray[triangleIndex].p2 = p2;
triangleArray[triangleIndex].bias0 = bias0;
triangleArray[triangleIndex].bias1 = bias1;
triangleArray[triangleIndex].bias2 = bias2;
}
kernel void TileKernel(const device float4* boxArray [[buffer(0)]],
device volatile atomic_uint* tileCounters [[buffer(1)]],
device uint* tilesArray [[buffer(2)]],
constant vector_uint2* viewport [[buffer(3)]],
uint gid [[thread_position_in_grid]])
{
uint2 tilesMatrixDim = (*viewport - 1) / RENDERER_TILE_SIZE + 1;
uint nTilesX = tilesMatrixDim.x;
uint nTilesY = tilesMatrixDim.y;
uint triangleIndex = gid;
uint4 box = uint4(floor(boxArray[triangleIndex]))/RENDERER_TILE_SIZE;
uint xMin = max((uint)0, box.x);
uint yMin = max((uint)0, box.y);
uint xMax = min(box.z, nTilesX-1);
uint yMax = min(box.w, nTilesY-1);
for(uint y = yMin; y <= yMax; y++)
{
for(uint x = xMin ; x <= xMax; x++)
{
uint tileIndex = y*nTilesX + x;
device uint* tileBuffer = tilesArray + tileIndex*RENDERER_TILE_BUFFER_SIZE;
uint counter = atomic_fetch_add_explicit(&(tileCounters[tileIndex]), 1, memory_order_relaxed);
tileBuffer[counter] = triangleIndex;
}
}
}
kernel void SortKernel(const device uint* tileCounters [[buffer(0)]],
const device mg_triangle_data* triangleArray [[buffer(1)]],
device uint* tilesArray [[buffer(2)]],
constant vector_uint2* viewport [[buffer(3)]],
uint gid [[thread_position_in_grid]])
{
uint tileIndex = gid;
device uint* tileBuffer = tilesArray + tileIndex*RENDERER_TILE_BUFFER_SIZE;
uint tileBufferSize = tileCounters[tileIndex];
for(int eltIndex=0; eltIndex < (int)tileBufferSize; eltIndex++)
{
uint elt = tileBuffer[eltIndex];
uint eltZIndex = triangleArray[elt].zIndex;
int backIndex = eltIndex-1;
for(; backIndex >= 0; backIndex--)
{
uint backElt = tileBuffer[backIndex];
uint backEltZIndex = triangleArray[backElt].zIndex;
if(eltZIndex >= backEltZIndex)
{
break;
}
else
{
tileBuffer[backIndex+1] = backElt;
}
}
tileBuffer[backIndex+1] = elt;
}
}
float orient2d(float2 a, float2 b, float2 c)
{
//////////////////////////////////////////////////////////////////////////////////////////
//TODO(martin): FIX this. This is a **horrible** quick hack to fix the precision issues
// arising when a, b, and c are close. But it degrades when a, c, and c
// are big. The proper solution is to change the expression to avoid
// precision loss but I'm too busy/lazy to do it now.
//////////////////////////////////////////////////////////////////////////////////////////
a *= 10;
b *= 10;
c *= 10;
return((b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x));
}
kernel void RenderKernel(texture2d<float, access::write> outTexture [[texture(0)]],
texture2d<float> texAtlas [[texture(1)]],
const device mg_vertex* vertexBuffer [[buffer(0)]],
device uint* tileCounters [[buffer(1)]],
const device uint* tilesArray [[buffer(2)]],
const device mg_triangle_data* triangleArray [[buffer(3)]],
const device float4* boxArray [[buffer(4)]],
constant vector_float4* clearColor [[buffer(5)]],
uint2 gid [[thread_position_in_grid]],
uint2 tgid [[threadgroup_position_in_grid]],
uint2 threadsPerThreadgroup [[threads_per_threadgroup]],
uint2 gridSize [[threads_per_grid]])
{
//TODO: guard against thread group size not equal to tile size?
const uint2 tilesMatrixDim = (gridSize - 1) / RENDERER_TILE_SIZE + 1;
// const uint2 tilePos = tgid * threadsPerThreadgroup / RENDERER_TILE_SIZE;
const uint2 tilePos = gid/RENDERER_TILE_SIZE;
const uint tileIndex = tilePos.y * tilesMatrixDim.x + tilePos.x;
const device uint* tileBuffer = tilesArray + tileIndex * RENDERER_TILE_BUFFER_SIZE;
const uint tileBufferSize = tileCounters[tileIndex];
//#define RENDERER_DEBUG_TILES
#ifdef RENDERER_DEBUG_TILES
//NOTE(martin): color code debug values and show the tile grid
uint nTileX = tilesMatrixDim.x;
uint nTileY = tilesMatrixDim.y;
if(tilePos.x == 2 && tilePos.y == 12)
{
outTexture.write(float4(1, 0.5, 1, 1), gid);
return;
}
if(nTileY != 13 || nTileX != 13)
{
outTexture.write(float4(1, 1, 0, 1), gid);
return;
}
if(tilePos.x > nTileX || tilePos.y > nTileY)
{
outTexture.write(float4(0, 1, 1, 1), gid);
return;
}
if((gid.x % RENDERER_TILE_SIZE == 0) || (gid.y % RENDERER_TILE_SIZE == 0))
{
outTexture.write(float4(0, 0, 0, 1), gid);
return;
}
if(tileBufferSize <= 0)
{
outTexture.write(float4(0, 1, 0, 1), gid);
return;
}
else
{
outTexture.write(float4(1, 0, 0, 1), gid);
return;
}
#endif
const float2 sampleOffsets[6] = { float2(5./12, 5./12),
float2(-3./12, 3./12),
float2(1./12, 1./12),
float2(3./12, -1./12),
float2(-5./12, -3./12),
float2(-1./12, -5./12)};
int zIndices[6];
uint flipCounts[6];
float4 pixelColors[6];
float4 nextColors[6];
for(int i=0; i<6; i++)
{
zIndices[i] = -1;
flipCounts[i] = 0;
pixelColors[i] = *clearColor;
nextColors[i] = *clearColor;
}
for(uint tileBufferIndex=0; tileBufferIndex < tileBufferSize; tileBufferIndex++)
{
float4 box = boxArray[tileBuffer[tileBufferIndex]];
const device mg_triangle_data* triangle = &triangleArray[tileBuffer[tileBufferIndex]];
float2 p0 = triangle->p0;
float2 p1 = triangle->p1;
float2 p2 = triangle->p2;
int bias0 = triangle->bias0;
int bias1 = triangle->bias1;
int bias2 = triangle->bias2;
const device mg_vertex* v0 = &(vertexBuffer[triangle->i0]);
const device mg_vertex* v1 = &(vertexBuffer[triangle->i1]);
const device mg_vertex* v2 = &(vertexBuffer[triangle->i2]);
float4 cubic0 = v0->cubic;
float4 cubic1 = v1->cubic;
float4 cubic2 = v2->cubic;
float2 uv0 = v0->uv;
float2 uv1 = v1->uv;
float2 uv2 = v2->uv;
int zIndex = v0->zIndex;
float4 color = v0->color;
for(int i=0; i<6; i++)
{
float2 samplePoint = (float2)gid + sampleOffsets[i];
//NOTE(martin): cull if pixel is outside box
if(samplePoint.x < box.x || samplePoint.x > box.z || samplePoint.y < box.y || samplePoint.y > box.w)
{
continue;
}
float w0 = orient2d(p1, p2, samplePoint);
float w1 = orient2d(p2, p0, samplePoint);
float w2 = orient2d(p0, p1, samplePoint);
if(((int)w0+bias0) >= 0 && ((int)w1+bias1) >= 0 && ((int)w2+bias2) >= 0)
{
float4 cubic = (cubic0*w0 + cubic1*w1 + cubic2*w2)/(w0+w1+w2);
float2 uv = (uv0*w0 + uv1*w1 + uv2*w2)/(w0+w1+w2);
constexpr sampler smp(mip_filter::nearest, mag_filter::linear, min_filter::linear);
float4 texColor = texAtlas.sample(smp, uv);
//TODO(martin): this is a quick and dirty fix for solid polygons where we use
// cubic = (1, 1, 1, 1) on all vertices, which can cause small errors to
// flip the sign.
// We should really use another value that always lead to <= 0, but we must
// make sure we never share these vertices with bezier shapes.
// Alternatively, an ugly (but maybe less than this one) solution would be
// to check if uvs are equal on all vertices of the triangle and always render
// those triangles.
float eps = 0.0001;
if(cubic.w*(cubic.x*cubic.x*cubic.x - cubic.y*cubic.z) <= eps)
{
if(zIndex == zIndices[i])
{
flipCounts[i]++;
}
else
{
if(flipCounts[i] & 0x01)
{
pixelColors[i] = nextColors[i];
}
float4 nextCol = color*texColor;
nextColors[i] = pixelColors[i]*(1-nextCol.a) +nextCol.a*nextCol;
zIndices[i] = zIndex;
flipCounts[i] = 1;
}
}
}
}
}
float4 out = float4(0, 0, 0, 0);
for(int i=0; i<6; i++)
{
if(flipCounts[i] & 0x01)
{
pixelColors[i] = nextColors[i];
}
out += pixelColors[i];
}
out = float4(out.xyz/6, 1);
outTexture.write(out, gid);
}
#include<metal_stdlib>
#include<simd/simd.h>
#include"metal_shader.h"
using namespace metal;
struct vs_out
{
float4 pos [[position]];
float2 uv;
};
vertex vs_out VertexShader(ushort vid [[vertex_id]])
{
vs_out out;
out.uv = float2((vid << 1) & 2, vid & 2);
out.pos = float4(out.uv * float2(2, 2) + float2(-1, -1), 0, 1);
return(out);
}
fragment float4 FragmentShader(vs_out i [[stage_in]], texture2d<float> tex [[texture(0)]])
{
constexpr sampler smp(mip_filter::nearest, mag_filter::linear, min_filter::linear);
return(float4(tex.sample(smp, i.uv).rgb, 1));
}
bool is_top_left(float2 a, float2 b)
{
return( (a.y == b.y && b.x < a.x)
||(b.y < a.y));
}
kernel void BoundingBoxKernel(constant mg_vertex* vertexBuffer [[buffer(0)]],
constant uint* indexBuffer [[buffer(1)]],
device mg_triangle_data* triangleArray [[buffer(2)]],
device float4* boxArray [[buffer(3)]],
constant float* contentsScaling [[buffer(4)]],
uint gid [[thread_position_in_grid]])
{
uint triangleIndex = gid;
uint vertexIndex = triangleIndex*3;
uint i0 = indexBuffer[vertexIndex];
uint i1 = indexBuffer[vertexIndex+1];
uint i2 = indexBuffer[vertexIndex+2];
float2 p0 = vertexBuffer[i0].pos * contentsScaling[0];
float2 p1 = vertexBuffer[i1].pos * contentsScaling[0];
float2 p2 = vertexBuffer[i2].pos * contentsScaling[0];
//NOTE(martin): compute triangle bounding box
float2 boxMin = min(min(p0, p1), p2);
float2 boxMax = max(max(p0, p1), p2);
//NOTE(martin): clip bounding box against clip rect
vector_float4 clip = contentsScaling[0]*vertexBuffer[indexBuffer[vertexIndex]].clip;
float2 clipMin(clip.x, clip.y);
float2 clipMax(clip.x + clip.z-1, clip.y + clip.w-1);
//NOTE(martin): intersect with current clip
boxMin = max(boxMin, clipMin);
boxMax = min(boxMax, clipMax);
//NOTE(martin): reorder triangle counter-clockwise and compute bias for each edge
float cw = (p1 - p0).x*(p2 - p0).y - (p1 - p0).y*(p2 - p0).x;
if(cw < 0)
{
uint tmpIndex = i1;
i1 = i2;
i2 = tmpIndex;
float2 tmpPoint = p1;
p1 = p2;
p2 = tmpPoint;
}
int bias0 = is_top_left(p1, p2) ? 0 : -1;
int bias1 = is_top_left(p2, p0) ? 0 : -1;
int bias2 = is_top_left(p0, p1) ? 0 : -1;
//NOTE(martin): fill triangle data
boxArray[triangleIndex] = float4(boxMin.x, boxMin.y, boxMax.x, boxMax.y);
triangleArray[triangleIndex].zIndex = vertexBuffer[i0].zIndex;
triangleArray[triangleIndex].i0 = i0;
triangleArray[triangleIndex].i1 = i1;
triangleArray[triangleIndex].i2 = i2;
triangleArray[triangleIndex].p0 = p0;
triangleArray[triangleIndex].p1 = p1;
triangleArray[triangleIndex].p2 = p2;
triangleArray[triangleIndex].bias0 = bias0;
triangleArray[triangleIndex].bias1 = bias1;
triangleArray[triangleIndex].bias2 = bias2;
}
kernel void TileKernel(const device float4* boxArray [[buffer(0)]],
device volatile atomic_uint* tileCounters [[buffer(1)]],
device uint* tilesArray [[buffer(2)]],
constant vector_uint2* viewport [[buffer(3)]],
uint gid [[thread_position_in_grid]])
{
uint2 tilesMatrixDim = (*viewport - 1) / RENDERER_TILE_SIZE + 1;
uint nTilesX = tilesMatrixDim.x;
uint nTilesY = tilesMatrixDim.y;
uint triangleIndex = gid;
uint4 box = uint4(floor(boxArray[triangleIndex]))/RENDERER_TILE_SIZE;
uint xMin = max((uint)0, box.x);
uint yMin = max((uint)0, box.y);
uint xMax = min(box.z, nTilesX-1);
uint yMax = min(box.w, nTilesY-1);
for(uint y = yMin; y <= yMax; y++)
{
for(uint x = xMin ; x <= xMax; x++)
{
uint tileIndex = y*nTilesX + x;
device uint* tileBuffer = tilesArray + tileIndex*RENDERER_TILE_BUFFER_SIZE;
uint counter = atomic_fetch_add_explicit(&(tileCounters[tileIndex]), 1, memory_order_relaxed);
tileBuffer[counter] = triangleIndex;
}
}
}
kernel void SortKernel(const device uint* tileCounters [[buffer(0)]],
const device mg_triangle_data* triangleArray [[buffer(1)]],
device uint* tilesArray [[buffer(2)]],
constant vector_uint2* viewport [[buffer(3)]],
uint gid [[thread_position_in_grid]])
{
uint tileIndex = gid;
device uint* tileBuffer = tilesArray + tileIndex*RENDERER_TILE_BUFFER_SIZE;
uint tileBufferSize = tileCounters[tileIndex];
for(int eltIndex=0; eltIndex < (int)tileBufferSize; eltIndex++)
{
uint elt = tileBuffer[eltIndex];
uint eltZIndex = triangleArray[elt].zIndex;
int backIndex = eltIndex-1;
for(; backIndex >= 0; backIndex--)
{
uint backElt = tileBuffer[backIndex];
uint backEltZIndex = triangleArray[backElt].zIndex;
if(eltZIndex >= backEltZIndex)
{
break;
}
else
{
tileBuffer[backIndex+1] = backElt;
}
}
tileBuffer[backIndex+1] = elt;
}
}
float orient2d(float2 a, float2 b, float2 c)
{
//////////////////////////////////////////////////////////////////////////////////////////
//TODO(martin): FIX this. This is a **horrible** quick hack to fix the precision issues
// arising when a, b, and c are close. But it degrades when a, c, and c
// are big. The proper solution is to change the expression to avoid
// precision loss but I'm too busy/lazy to do it now.
//////////////////////////////////////////////////////////////////////////////////////////
a *= 10;
b *= 10;
c *= 10;
return((b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x));
}
kernel void RenderKernel(texture2d<float, access::write> outTexture [[texture(0)]],
texture2d<float> texAtlas [[texture(1)]],
const device mg_vertex* vertexBuffer [[buffer(0)]],
device uint* tileCounters [[buffer(1)]],
const device uint* tilesArray [[buffer(2)]],
const device mg_triangle_data* triangleArray [[buffer(3)]],
const device float4* boxArray [[buffer(4)]],
constant vector_float4* clearColor [[buffer(5)]],
uint2 gid [[thread_position_in_grid]],
uint2 tgid [[threadgroup_position_in_grid]],
uint2 threadsPerThreadgroup [[threads_per_threadgroup]],
uint2 gridSize [[threads_per_grid]])
{
//TODO: guard against thread group size not equal to tile size?
const uint2 tilesMatrixDim = (gridSize - 1) / RENDERER_TILE_SIZE + 1;
// const uint2 tilePos = tgid * threadsPerThreadgroup / RENDERER_TILE_SIZE;
const uint2 tilePos = gid/RENDERER_TILE_SIZE;
const uint tileIndex = tilePos.y * tilesMatrixDim.x + tilePos.x;
const device uint* tileBuffer = tilesArray + tileIndex * RENDERER_TILE_BUFFER_SIZE;
const uint tileBufferSize = tileCounters[tileIndex];
//#define RENDERER_DEBUG_TILES
#ifdef RENDERER_DEBUG_TILES
//NOTE(martin): color code debug values and show the tile grid
uint nTileX = tilesMatrixDim.x;
uint nTileY = tilesMatrixDim.y;
if(tilePos.x == 2 && tilePos.y == 12)
{
outTexture.write(float4(1, 0.5, 1, 1), gid);
return;
}
if(nTileY != 13 || nTileX != 13)
{
outTexture.write(float4(1, 1, 0, 1), gid);
return;
}
if(tilePos.x > nTileX || tilePos.y > nTileY)
{
outTexture.write(float4(0, 1, 1, 1), gid);
return;
}
if((gid.x % RENDERER_TILE_SIZE == 0) || (gid.y % RENDERER_TILE_SIZE == 0))
{
outTexture.write(float4(0, 0, 0, 1), gid);
return;
}
if(tileBufferSize <= 0)
{
outTexture.write(float4(0, 1, 0, 1), gid);
return;
}
else
{
outTexture.write(float4(1, 0, 0, 1), gid);
return;
}
#endif
const float2 sampleOffsets[6] = { float2(5./12, 5./12),
float2(-3./12, 3./12),
float2(1./12, 1./12),
float2(3./12, -1./12),
float2(-5./12, -3./12),
float2(-1./12, -5./12)};
int zIndices[6];
uint flipCounts[6];
float4 pixelColors[6];
float4 nextColors[6];
for(int i=0; i<6; i++)
{
zIndices[i] = -1;
flipCounts[i] = 0;
pixelColors[i] = *clearColor;
nextColors[i] = *clearColor;
}
for(uint tileBufferIndex=0; tileBufferIndex < tileBufferSize; tileBufferIndex++)
{
float4 box = boxArray[tileBuffer[tileBufferIndex]];
const device mg_triangle_data* triangle = &triangleArray[tileBuffer[tileBufferIndex]];
float2 p0 = triangle->p0;
float2 p1 = triangle->p1;
float2 p2 = triangle->p2;
int bias0 = triangle->bias0;
int bias1 = triangle->bias1;
int bias2 = triangle->bias2;
const device mg_vertex* v0 = &(vertexBuffer[triangle->i0]);
const device mg_vertex* v1 = &(vertexBuffer[triangle->i1]);
const device mg_vertex* v2 = &(vertexBuffer[triangle->i2]);
float4 cubic0 = v0->cubic;
float4 cubic1 = v1->cubic;
float4 cubic2 = v2->cubic;
float2 uv0 = v0->uv;
float2 uv1 = v1->uv;
float2 uv2 = v2->uv;
int zIndex = v0->zIndex;
float4 color = v0->color;
for(int i=0; i<6; i++)
{
float2 samplePoint = (float2)gid + sampleOffsets[i];
//NOTE(martin): cull if pixel is outside box
if(samplePoint.x < box.x || samplePoint.x > box.z || samplePoint.y < box.y || samplePoint.y > box.w)
{
continue;
}
float w0 = orient2d(p1, p2, samplePoint);
float w1 = orient2d(p2, p0, samplePoint);
float w2 = orient2d(p0, p1, samplePoint);
if(((int)w0+bias0) >= 0 && ((int)w1+bias1) >= 0 && ((int)w2+bias2) >= 0)
{
float4 cubic = (cubic0*w0 + cubic1*w1 + cubic2*w2)/(w0+w1+w2);
float2 uv = (uv0*w0 + uv1*w1 + uv2*w2)/(w0+w1+w2);
constexpr sampler smp(mip_filter::nearest, mag_filter::linear, min_filter::linear);
float4 texColor = texAtlas.sample(smp, uv);
//TODO(martin): this is a quick and dirty fix for solid polygons where we use
// cubic = (1, 1, 1, 1) on all vertices, which can cause small errors to
// flip the sign.
// We should really use another value that always lead to <= 0, but we must
// make sure we never share these vertices with bezier shapes.
// Alternatively, an ugly (but maybe less than this one) solution would be
// to check if uvs are equal on all vertices of the triangle and always render
// those triangles.
float eps = 0.0001;
if(cubic.w*(cubic.x*cubic.x*cubic.x - cubic.y*cubic.z) <= eps)
{
if(zIndex == zIndices[i])
{
flipCounts[i]++;
}
else
{
if(flipCounts[i] & 0x01)
{
pixelColors[i] = nextColors[i];
}
float4 nextCol = color*texColor;
nextColors[i] = pixelColors[i]*(1-nextCol.a) +nextCol.a*nextCol;
zIndices[i] = zIndex;
flipCounts[i] = 1;
}
}
}
}
}
float4 out = float4(0, 0, 0, 0);
for(int i=0; i<6; i++)
{
if(flipCounts[i] & 0x01)
{
pixelColors[i] = nextColors[i];
}
out += pixelColors[i];
}
out = float4(out.xyz/6, 1);
outTexture.write(out, gid);
}

View File

@ -54,6 +54,7 @@
#include"win32_app.c"
// #include"win32_gl_surface.c"
#include"win32_gles_surface.c"
#include"gles_canvas.c"
#elif defined(OS_MACOS)
//NOTE: macos application layer is defined in milepost.m
#else

View File

@ -40,8 +40,17 @@
#if defined(OS_WIN64) || defined(OS_WIN32)
#define WIN32_GL_LOADER_API
// #include"win32_gl_loader.h"
#if MG_IMPLEMENTS_BACKEND_GLES
#include"win32_gles_surface.h"
#endif
#endif
#if MG_IMPLEMENTS_BACKEND_METAL
#include"metal_surface.h"
#endif
//----------------------------------------------------------------
// graphics/ui layer
//----------------------------------------------------------------

View File

@ -1,349 +1,349 @@
/************************************************************//**
*
* @file: mp_app_internal.c
* @author: Martin Fouilleul
* @date: 23/12/2022
* @revision:
*
*****************************************************************/
#include"mp_app_internal.h"
#define LOG_SUBSYSTEM "Application"
mp_app __mpApp = {0};
//---------------------------------------------------------------
// Window handles
//---------------------------------------------------------------
void mp_init_window_handles()
{
ListInit(&__mpApp.windowFreeList);
for(int i=0; i<MP_APP_MAX_WINDOWS; i++)
{
__mpApp.windowPool[i].generation = 1;
ListAppend(&__mpApp.windowFreeList, &__mpApp.windowPool[i].freeListElt);
}
}
bool mp_window_handle_is_null(mp_window window)
{
return(window.h == 0);
}
mp_window mp_window_null_handle()
{
return((mp_window){.h = 0});
}
mp_window_data* mp_window_alloc()
{
return(ListPopEntry(&__mpApp.windowFreeList, mp_window_data, freeListElt));
}
mp_window_data* mp_window_ptr_from_handle(mp_window handle)
{
u32 index = handle.h>>32;
u32 generation = handle.h & 0xffffffff;
if(index >= MP_APP_MAX_WINDOWS)
{
return(0);
}
mp_window_data* window = &__mpApp.windowPool[index];
if(window->generation != generation)
{
return(0);
}
else
{
return(window);
}
}
mp_window mp_window_handle_from_ptr(mp_window_data* window)
{
DEBUG_ASSERT( (window - __mpApp.windowPool) >= 0
&& (window - __mpApp.windowPool) < MP_APP_MAX_WINDOWS);
u64 h = ((u64)(window - __mpApp.windowPool))<<32
| ((u64)window->generation);
return((mp_window){h});
}
void mp_window_recycle_ptr(mp_window_data* window)
{
window->generation++;
ListPush(&__mpApp.windowFreeList, &window->freeListElt);
}
//---------------------------------------------------------------
// Init
//---------------------------------------------------------------
static void mp_init_common()
{
mp_init_window_handles();
ringbuffer_init(&__mpApp.eventQueue, 16);
}
static void mp_terminate_common()
{
ringbuffer_cleanup(&__mpApp.eventQueue);
}
//---------------------------------------------------------------
// Event handling
//---------------------------------------------------------------
void mp_queue_event(mp_event* event)
{
if(ringbuffer_write_available(&__mpApp.eventQueue) < sizeof(mp_event))
{
LOG_ERROR("event queue full\n");
}
else
{
u32 written = ringbuffer_write(&__mpApp.eventQueue, sizeof(mp_event), (u8*)event);
DEBUG_ASSERT(written == sizeof(mp_event));
}
}
bool mp_next_event(mp_event* event)
{
//NOTE pop and return event from queue
if(ringbuffer_read_available(&__mpApp.eventQueue) >= sizeof(mp_event))
{
u64 read = ringbuffer_read(&__mpApp.eventQueue, sizeof(mp_event), (u8*)event);
DEBUG_ASSERT(read == sizeof(mp_event));
return(true);
}
else
{
return(false);
}
}
//---------------------------------------------------------------
// Input state updating
//---------------------------------------------------------------
static void mp_update_key_state(mp_key_state* key, bool down)
{
u64 frameCounter = __mpApp.inputState.frameCounter;
if(key->lastUpdate != frameCounter)
{
key->transitionCounter = 0;
key->clicked = false;
key->doubleClicked = false;
key->lastUpdate = frameCounter;
}
if(key->down == down)
{
key->transitionCounter++;
}
key->down = down;
}
static void mp_update_mouse_move(f32 x, f32 y, f32 deltaX, f32 deltaY)
{
u64 frameCounter = __mpApp.inputState.frameCounter;
mp_mouse_state* mouse = &__mpApp.inputState.mouse;
if(mouse->lastUpdate != frameCounter)
{
mouse->delta = (vec2){0, 0};
mouse->wheel = (vec2){0, 0};
mouse->lastUpdate = frameCounter;
}
mouse->pos = (vec2){x, y};
mouse->delta.x += deltaX;
mouse->delta.y += deltaY;
}
static void mp_update_mouse_wheel(f32 deltaX, f32 deltaY)
{
u64 frameCounter = __mpApp.inputState.frameCounter;
mp_mouse_state* mouse = &__mpApp.inputState.mouse;
if(mouse->lastUpdate != frameCounter)
{
mouse->delta = (vec2){0, 0};
mouse->wheel = (vec2){0, 0};
mouse->lastUpdate = frameCounter;
}
mouse->wheel.x += deltaX;
mouse->wheel.y += deltaY;
}
static void mp_update_text(utf32 codepoint)
{
u64 frameCounter = __mpApp.inputState.frameCounter;
mp_text_state* text = &__mpApp.inputState.text;
if(text->lastUpdate != frameCounter)
{
text->codePoints.len = 0;
text->lastUpdate = frameCounter;
}
text->codePoints.ptr = text->backing;
if(text->codePoints.len < MP_INPUT_TEXT_BACKING_SIZE)
{
text->codePoints.ptr[text->codePoints.len] = codepoint;
text->codePoints.len++;
}
else
{
LOG_WARNING("too many input codepoints per frame, dropping input");
}
}
//--------------------------------------------------------------------
// Input state polling
//--------------------------------------------------------------------
mp_key_state mp_input_get_key_state(mp_key_code key)
{
if(key <= MP_KEY_COUNT)
{
return(__mpApp.inputState.keyboard.keys[key]);
}
else
{
return((mp_key_state){0});
}
}
mp_key_state mp_input_get_mouse_button_state(mp_mouse_button button)
{
if(button <= MP_MOUSE_BUTTON_COUNT)
{
return(__mpApp.inputState.mouse.buttons[button]);
}
else
{
return((mp_key_state){0});
}
}
bool mp_input_check_key_transition(mp_key_state* key, bool pressed)
{
bool res = ( (key->lastUpdate == __mpApp.inputState.frameCounter)
&& key->transitionCounter
&&(key->down == pressed || key->transitionCounter > 1));
return(res);
}
bool mp_input_key_down(mp_key_code key)
{
mp_key_state state = mp_input_get_key_state(key);
return(state.down);
}
bool mp_input_key_pressed(mp_key_code key)
{
mp_key_state state = mp_input_get_key_state(key);
bool res = mp_input_check_key_transition(&state, true);
return(res);
}
bool mp_input_key_released(mp_key_code key)
{
mp_key_state state = mp_input_get_key_state(key);
bool res = mp_input_check_key_transition(&state, false);
return(res);
}
bool mp_input_mouse_down(mp_mouse_button button)
{
mp_key_state state = mp_input_get_mouse_button_state(button);
return(state.down);
}
bool mp_input_mouse_pressed(mp_mouse_button button)
{
mp_key_state state = mp_input_get_mouse_button_state(button);
bool res = mp_input_check_key_transition(&state, true);
return(res);
}
bool mp_input_mouse_released(mp_mouse_button button)
{
mp_key_state state = mp_input_get_mouse_button_state(button);
bool res = mp_input_check_key_transition(&state, false);
return(res);
}
bool mp_input_mouse_clicked(mp_mouse_button button)
{
mp_key_state state = mp_input_get_mouse_button_state(button);
return(state.clicked);
}
bool mp_input_mouse_double_clicked(mp_mouse_button button)
{
mp_key_state state = mp_input_get_mouse_button_state(button);
if(state.lastUpdate == __mpApp.inputState.frameCounter)
{
return(state.doubleClicked);
}
else
{
return(false);
}
}
mp_key_mods mp_input_key_mods()
{
return(__mpApp.inputState.keyboard.mods);
}
vec2 mp_input_mouse_position()
{
return(__mpApp.inputState.mouse.pos);
}
vec2 mp_input_mouse_delta()
{
if(__mpApp.inputState.mouse.lastUpdate == __mpApp.inputState.frameCounter)
{
return(__mpApp.inputState.mouse.delta);
}
else
{
return((vec2){0, 0});
}
}
vec2 mp_input_mouse_wheel()
{
if(__mpApp.inputState.mouse.lastUpdate == __mpApp.inputState.frameCounter)
{
return(__mpApp.inputState.mouse.wheel);
}
else
{
return((vec2){0, 0});
}
}
str32 mp_input_text_utf32(mem_arena* arena)
{
str32 res = {0};
if(__mpApp.inputState.text.lastUpdate == __mpApp.inputState.frameCounter)
{
res = str32_push_copy(arena, __mpApp.inputState.text.codePoints);
}
return(res);
}
str8 mp_input_text_utf8(mem_arena* arena)
{
str8 res = {0};
if(__mpApp.inputState.text.lastUpdate == __mpApp.inputState.frameCounter)
{
res = utf8_push_from_codepoints(arena, __mpApp.inputState.text.codePoints);
}
return(res);
}
#undef LOG_SUBSYSTEM
/************************************************************//**
*
* @file: mp_app_internal.c
* @author: Martin Fouilleul
* @date: 23/12/2022
* @revision:
*
*****************************************************************/
#include"mp_app_internal.h"
#define LOG_SUBSYSTEM "Application"
mp_app __mpApp = {0};
//---------------------------------------------------------------
// Window handles
//---------------------------------------------------------------
void mp_init_window_handles()
{
ListInit(&__mpApp.windowFreeList);
for(int i=0; i<MP_APP_MAX_WINDOWS; i++)
{
__mpApp.windowPool[i].generation = 1;
ListAppend(&__mpApp.windowFreeList, &__mpApp.windowPool[i].freeListElt);
}
}
bool mp_window_handle_is_null(mp_window window)
{
return(window.h == 0);
}
mp_window mp_window_null_handle()
{
return((mp_window){.h = 0});
}
mp_window_data* mp_window_alloc()
{
return(ListPopEntry(&__mpApp.windowFreeList, mp_window_data, freeListElt));
}
mp_window_data* mp_window_ptr_from_handle(mp_window handle)
{
u32 index = handle.h>>32;
u32 generation = handle.h & 0xffffffff;
if(index >= MP_APP_MAX_WINDOWS)
{
return(0);
}
mp_window_data* window = &__mpApp.windowPool[index];
if(window->generation != generation)
{
return(0);
}
else
{
return(window);
}
}
mp_window mp_window_handle_from_ptr(mp_window_data* window)
{
DEBUG_ASSERT( (window - __mpApp.windowPool) >= 0
&& (window - __mpApp.windowPool) < MP_APP_MAX_WINDOWS);
u64 h = ((u64)(window - __mpApp.windowPool))<<32
| ((u64)window->generation);
return((mp_window){h});
}
void mp_window_recycle_ptr(mp_window_data* window)
{
window->generation++;
ListPush(&__mpApp.windowFreeList, &window->freeListElt);
}
//---------------------------------------------------------------
// Init
//---------------------------------------------------------------
static void mp_init_common()
{
mp_init_window_handles();
ringbuffer_init(&__mpApp.eventQueue, 16);
}
static void mp_terminate_common()
{
ringbuffer_cleanup(&__mpApp.eventQueue);
}
//---------------------------------------------------------------
// Event handling
//---------------------------------------------------------------
void mp_queue_event(mp_event* event)
{
if(ringbuffer_write_available(&__mpApp.eventQueue) < sizeof(mp_event))
{
LOG_ERROR("event queue full\n");
}
else
{
u32 written = ringbuffer_write(&__mpApp.eventQueue, sizeof(mp_event), (u8*)event);
DEBUG_ASSERT(written == sizeof(mp_event));
}
}
bool mp_next_event(mp_event* event)
{
//NOTE pop and return event from queue
if(ringbuffer_read_available(&__mpApp.eventQueue) >= sizeof(mp_event))
{
u64 read = ringbuffer_read(&__mpApp.eventQueue, sizeof(mp_event), (u8*)event);
DEBUG_ASSERT(read == sizeof(mp_event));
return(true);
}
else
{
return(false);
}
}
//---------------------------------------------------------------
// Input state updating
//---------------------------------------------------------------
static void mp_update_key_state(mp_key_state* key, bool down)
{
u64 frameCounter = __mpApp.inputState.frameCounter;
if(key->lastUpdate != frameCounter)
{
key->transitionCounter = 0;
key->clicked = false;
key->doubleClicked = false;
key->lastUpdate = frameCounter;
}
if(key->down == down)
{
key->transitionCounter++;
}
key->down = down;
}
static void mp_update_mouse_move(f32 x, f32 y, f32 deltaX, f32 deltaY)
{
u64 frameCounter = __mpApp.inputState.frameCounter;
mp_mouse_state* mouse = &__mpApp.inputState.mouse;
if(mouse->lastUpdate != frameCounter)
{
mouse->delta = (vec2){0, 0};
mouse->wheel = (vec2){0, 0};
mouse->lastUpdate = frameCounter;
}
mouse->pos = (vec2){x, y};
mouse->delta.x += deltaX;
mouse->delta.y += deltaY;
}
static void mp_update_mouse_wheel(f32 deltaX, f32 deltaY)
{
u64 frameCounter = __mpApp.inputState.frameCounter;
mp_mouse_state* mouse = &__mpApp.inputState.mouse;
if(mouse->lastUpdate != frameCounter)
{
mouse->delta = (vec2){0, 0};
mouse->wheel = (vec2){0, 0};
mouse->lastUpdate = frameCounter;
}
mouse->wheel.x += deltaX;
mouse->wheel.y += deltaY;
}
static void mp_update_text(utf32 codepoint)
{
u64 frameCounter = __mpApp.inputState.frameCounter;
mp_text_state* text = &__mpApp.inputState.text;
if(text->lastUpdate != frameCounter)
{
text->codePoints.len = 0;
text->lastUpdate = frameCounter;
}
text->codePoints.ptr = text->backing;
if(text->codePoints.len < MP_INPUT_TEXT_BACKING_SIZE)
{
text->codePoints.ptr[text->codePoints.len] = codepoint;
text->codePoints.len++;
}
else
{
LOG_WARNING("too many input codepoints per frame, dropping input");
}
}
//--------------------------------------------------------------------
// Input state polling
//--------------------------------------------------------------------
mp_key_state mp_input_get_key_state(mp_key_code key)
{
if(key <= MP_KEY_COUNT)
{
return(__mpApp.inputState.keyboard.keys[key]);
}
else
{
return((mp_key_state){0});
}
}
mp_key_state mp_input_get_mouse_button_state(mp_mouse_button button)
{
if(button <= MP_MOUSE_BUTTON_COUNT)
{
return(__mpApp.inputState.mouse.buttons[button]);
}
else
{
return((mp_key_state){0});
}
}
bool mp_input_check_key_transition(mp_key_state* key, bool pressed)
{
bool res = ( (key->lastUpdate == __mpApp.inputState.frameCounter)
&& key->transitionCounter
&&(key->down == pressed || key->transitionCounter > 1));
return(res);
}
bool mp_input_key_down(mp_key_code key)
{
mp_key_state state = mp_input_get_key_state(key);
return(state.down);
}
bool mp_input_key_pressed(mp_key_code key)
{
mp_key_state state = mp_input_get_key_state(key);
bool res = mp_input_check_key_transition(&state, true);
return(res);
}
bool mp_input_key_released(mp_key_code key)
{
mp_key_state state = mp_input_get_key_state(key);
bool res = mp_input_check_key_transition(&state, false);
return(res);
}
bool mp_input_mouse_down(mp_mouse_button button)
{
mp_key_state state = mp_input_get_mouse_button_state(button);
return(state.down);
}
bool mp_input_mouse_pressed(mp_mouse_button button)
{
mp_key_state state = mp_input_get_mouse_button_state(button);
bool res = mp_input_check_key_transition(&state, true);
return(res);
}
bool mp_input_mouse_released(mp_mouse_button button)
{
mp_key_state state = mp_input_get_mouse_button_state(button);
bool res = mp_input_check_key_transition(&state, false);
return(res);
}
bool mp_input_mouse_clicked(mp_mouse_button button)
{
mp_key_state state = mp_input_get_mouse_button_state(button);
return(state.clicked);
}
bool mp_input_mouse_double_clicked(mp_mouse_button button)
{
mp_key_state state = mp_input_get_mouse_button_state(button);
if(state.lastUpdate == __mpApp.inputState.frameCounter)
{
return(state.doubleClicked);
}
else
{
return(false);
}
}
mp_key_mods mp_input_key_mods()
{
return(__mpApp.inputState.keyboard.mods);
}
vec2 mp_input_mouse_position()
{
return(__mpApp.inputState.mouse.pos);
}
vec2 mp_input_mouse_delta()
{
if(__mpApp.inputState.mouse.lastUpdate == __mpApp.inputState.frameCounter)
{
return(__mpApp.inputState.mouse.delta);
}
else
{
return((vec2){0, 0});
}
}
vec2 mp_input_mouse_wheel()
{
if(__mpApp.inputState.mouse.lastUpdate == __mpApp.inputState.frameCounter)
{
return(__mpApp.inputState.mouse.wheel);
}
else
{
return((vec2){0, 0});
}
}
str32 mp_input_text_utf32(mem_arena* arena)
{
str32 res = {0};
if(__mpApp.inputState.text.lastUpdate == __mpApp.inputState.frameCounter)
{
res = str32_push_copy(arena, __mpApp.inputState.text.codePoints);
}
return(res);
}
str8 mp_input_text_utf8(mem_arena* arena)
{
str8 res = {0};
if(__mpApp.inputState.text.lastUpdate == __mpApp.inputState.frameCounter)
{
res = utf8_push_from_codepoints(arena, __mpApp.inputState.text.codePoints);
}
return(res);
}
#undef LOG_SUBSYSTEM

View File

@ -1,152 +1,152 @@
/************************************************************//**
*
* @file: mp_app_internal.h
* @author: Martin Fouilleul
* @date: 23/12/2022
* @revision:
*
*****************************************************************/
#ifndef __MP_APP_INTERNAL_H_
#define __MP_APP_INTERNAL_H_
#include"platform.h"
#include"ringbuffer.h"
#if defined(OS_WIN64) || defined(OS_WIN32)
#include"win32_app.h"
#elif defined(OS_MACOS)
#include"osx_app.h"
#else
#error "platform not supported yet"
#endif
//---------------------------------------------------------------
// Input State
//---------------------------------------------------------------
typedef struct mp_key_utf8
{
u8 labelLen;
char label[8];
} mp_key_utf8;
typedef struct mp_key_state
{
u64 lastUpdate;
u32 transitionCounter;
bool down;
bool clicked;
bool doubleClicked;
} mp_key_state;
typedef struct mp_keyboard_state
{
mp_key_state keys[MP_KEY_COUNT];
mp_key_mods mods;
} mp_keyboard_state;
typedef struct mp_mouse_state
{
u64 lastUpdate;
bool posValid;
vec2 pos;
vec2 delta;
vec2 wheel;
union
{
mp_key_state buttons[MP_MOUSE_BUTTON_COUNT];
struct
{
mp_key_state left;
mp_key_state right;
mp_key_state middle;
mp_key_state ext1;
mp_key_state ext2;
};
};
} mp_mouse_state;
enum { MP_INPUT_TEXT_BACKING_SIZE = 64 };
typedef struct mp_text_state
{
u64 lastUpdate;
utf32 backing[MP_INPUT_TEXT_BACKING_SIZE];
str32 codePoints;
} mp_text_state;
typedef struct mp_input_state
{
u64 frameCounter;
mp_keyboard_state keyboard;
mp_mouse_state mouse;
mp_text_state text;
} mp_input_state;
//---------------------------------------------------------------
// Window structure
//---------------------------------------------------------------
typedef struct mp_frame_stats
{
f64 start;
f64 workTime;
f64 remainingTime;
f64 targetFramePeriod;
} mp_frame_stats;
typedef struct mp_window_data
{
list_elt freeListElt;
u32 generation;
mp_rect contentRect;
mp_rect frameRect;
mp_window_style style;
bool shouldClose; //TODO could be in status flags
bool hidden;
bool minimized;
MP_PLATFORM_WINDOW_DATA
} mp_window_data;
//---------------------------------------------------------------
// Global App State
//---------------------------------------------------------------
enum { MP_APP_MAX_WINDOWS = 128 };
typedef struct mp_app
{
bool init;
bool shouldQuit;
bool minimized;
str8 pendingPathDrop;
mem_arena eventArena;
ringbuffer eventQueue;
mp_frame_stats frameStats;
mp_window_data windowPool[MP_APP_MAX_WINDOWS];
list_info windowFreeList;
mp_live_resize_callback liveResizeCallback;
void* liveResizeData;
mp_input_state inputState;
mp_key_utf8 keyLabels[256];
int keyCodes[256];
int nativeKeys[MP_KEY_COUNT];
MP_PLATFORM_APP_DATA
} mp_app;
#endif // __MP_APP_INTERNAL_H_
/************************************************************//**
*
* @file: mp_app_internal.h
* @author: Martin Fouilleul
* @date: 23/12/2022
* @revision:
*
*****************************************************************/
#ifndef __MP_APP_INTERNAL_H_
#define __MP_APP_INTERNAL_H_
#include"platform.h"
#include"ringbuffer.h"
#if defined(OS_WIN64) || defined(OS_WIN32)
#include"win32_app.h"
#elif defined(OS_MACOS)
#include"osx_app.h"
#else
#error "platform not supported yet"
#endif
//---------------------------------------------------------------
// Input State
//---------------------------------------------------------------
typedef struct mp_key_utf8
{
u8 labelLen;
char label[8];
} mp_key_utf8;
typedef struct mp_key_state
{
u64 lastUpdate;
u32 transitionCounter;
bool down;
bool clicked;
bool doubleClicked;
} mp_key_state;
typedef struct mp_keyboard_state
{
mp_key_state keys[MP_KEY_COUNT];
mp_key_mods mods;
} mp_keyboard_state;
typedef struct mp_mouse_state
{
u64 lastUpdate;
bool posValid;
vec2 pos;
vec2 delta;
vec2 wheel;
union
{
mp_key_state buttons[MP_MOUSE_BUTTON_COUNT];
struct
{
mp_key_state left;
mp_key_state right;
mp_key_state middle;
mp_key_state ext1;
mp_key_state ext2;
};
};
} mp_mouse_state;
enum { MP_INPUT_TEXT_BACKING_SIZE = 64 };
typedef struct mp_text_state
{
u64 lastUpdate;
utf32 backing[MP_INPUT_TEXT_BACKING_SIZE];
str32 codePoints;
} mp_text_state;
typedef struct mp_input_state
{
u64 frameCounter;
mp_keyboard_state keyboard;
mp_mouse_state mouse;
mp_text_state text;
} mp_input_state;
//---------------------------------------------------------------
// Window structure
//---------------------------------------------------------------
typedef struct mp_frame_stats
{
f64 start;
f64 workTime;
f64 remainingTime;
f64 targetFramePeriod;
} mp_frame_stats;
typedef struct mp_window_data
{
list_elt freeListElt;
u32 generation;
mp_rect contentRect;
mp_rect frameRect;
mp_window_style style;
bool shouldClose; //TODO could be in status flags
bool hidden;
bool minimized;
MP_PLATFORM_WINDOW_DATA
} mp_window_data;
//---------------------------------------------------------------
// Global App State
//---------------------------------------------------------------
enum { MP_APP_MAX_WINDOWS = 128 };
typedef struct mp_app
{
bool init;
bool shouldQuit;
bool minimized;
str8 pendingPathDrop;
mem_arena eventArena;
ringbuffer eventQueue;
mp_frame_stats frameStats;
mp_window_data windowPool[MP_APP_MAX_WINDOWS];
list_info windowFreeList;
mp_live_resize_callback liveResizeCallback;
void* liveResizeData;
mp_input_state inputState;
mp_key_utf8 keyLabels[512];
int keyCodes[512];
int nativeKeys[MP_KEY_COUNT];
MP_PLATFORM_APP_DATA
} mp_app;
#endif // __MP_APP_INTERNAL_H_

View File

@ -1,32 +1,32 @@
/************************************************************//**
*
* @file: osx_gles_surface.cpp
* @author: Martin Fouilleul
* @date: 18/08/2022
* @revision:
*
*****************************************************************/
//#include<Cocoa/Cocoa.h>
//#include <Foundation/Foundation.h>
#include <QuartzCore/QuartzCore.h>
#include<GLES3/gl32.h>
#define EGL_EGLEXT_PROTOTYPES
#include<EGL/egl.h>
#include<EGL/eglext.h>
#include"graphics_internal.h"
typedef struct mg_gles_surface
{
mg_surface_data interface;
NSView* view;
CALayer* layer;
EGLDisplay eglDisplay;
EGLConfig eglConfig;
EGLContext eglContext;
EGLSurface eglSurface;
EGLint numConfigs;
} mg_gles_surface;
/************************************************************//**
*
* @file: osx_gles_surface.cpp
* @author: Martin Fouilleul
* @date: 18/08/2022
* @revision:
*
*****************************************************************/
//#include<Cocoa/Cocoa.h>
//#include <Foundation/Foundation.h>
#include <QuartzCore/QuartzCore.h>
#include<GLES3/gl3.h>
#define EGL_EGLEXT_PROTOTYPES
#include<EGL/egl.h>
#include<EGL/eglext.h>
#include"graphics_internal.h"
typedef struct mg_gles_surface
{
mg_surface_data interface;
NSView* view;
CALayer* layer;
EGLDisplay eglDisplay;
EGLConfig eglConfig;
EGLContext eglContext;
EGLSurface eglSurface;
EGLint numConfigs;
} mg_gles_surface;

View File

@ -1,184 +1,184 @@
/************************************************************//**
*
* @file: osx_gles_surface.cpp
* @author: Martin Fouilleul
* @date: 18/08/2022
* @revision:
*
*****************************************************************/
//#include<Cocoa/Cocoa.h>
//#include <Foundation/Foundation.h>
#include <QuartzCore/QuartzCore.h>
#include<GLES3/gl32.h>
#define EGL_EGLEXT_PROTOTYPES
#include<EGL/egl.h>
#include<EGL/eglext.h>
#include"graphics_internal.h"
#include"osx_gles_surface.h"
void mg_gles_surface_destroy(mg_surface_data* interface)
{
//////////////////////////////////////////////////
//TODO
//////////////////////////////////////////////////
}
void mg_gles_surface_prepare(mg_surface_data* interface)
{
mg_gles_surface* surface = (mg_gles_surface*)interface;
eglMakeCurrent(surface->eglDisplay, surface->eglSurface, surface->eglSurface, surface->eglContext);
}
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)
{
mg_gles_surface* surface = (mg_gles_surface*)interface;
f32 scale = surface->layer.contentsScale;
CGRect bounds = (CGRect){{frame.x * scale, frame.y * scale}, {.width = frame.w*scale, .height = frame.h*scale}};
[surface->layer setBounds: bounds];
}
mp_rect mg_gles_surface_get_frame(mg_surface_data* interface)
{
mg_gles_surface* surface = (mg_gles_surface*)interface;
f32 scale = surface->layer.contentsScale;
CGRect bounds = [surface->layer bounds];
mp_rect rect = {bounds.origin.x / scale, bounds.origin.y / scale, bounds.size.width / scale, bounds.size.height / scale};
return(rect);
}
void mg_gles_surface_set_hidden(mg_surface_data* interface, bool hidden)
{
//TODO: doesn't make sense for an offscreen surface?
}
bool mg_gles_surface_get_hidden(mg_surface_data* interface)
{
//TODO: doesn't make sense for an offscreen surface?
return(false);
}
vec2 mg_gles_surface_get_size(mg_surface_data* interface)
{
mg_gles_surface* surface = (mg_gles_surface*)interface;
CGRect bounds = [surface->layer bounds];
f32 scale = surface->layer.contentsScale;
vec2 res = {bounds.size.width/scale, bounds.size.height/scale};
return(res);
}
mg_surface mg_gles_surface_create_with_view(u32 width, u32 height, NSView* view)
{
mg_gles_surface* surface = malloc_type(mg_gles_surface);
memset(surface, 0, sizeof(mg_gles_surface));
surface->interface.backend = MG_BACKEND_GLES;
surface->interface.destroy = mg_gles_surface_destroy;
surface->interface.prepare = mg_gles_surface_prepare;
surface->interface.present = mg_gles_surface_present;
surface->interface.getFrame = mg_gles_surface_get_frame;
surface->interface.setFrame = mg_gles_surface_set_frame;
surface->interface.getHidden = mg_gles_surface_get_hidden;
surface->interface.setHidden = mg_gles_surface_set_hidden;
surface->view = view;
@autoreleasepool
{
surface->layer = [[CALayer alloc] init];
[surface->layer retain];
[surface->layer setBounds:CGRectMake(0, 0, width, height)];
if(surface->view)
{
[surface->view setWantsLayer: YES];
surface->view.layer = surface->layer;
}
}
EGLAttrib displayAttribs[] = {
//NOTE: we need to explicitly set EGL_PLATFORM_ANGLE_TYPE_ANGLE to EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE, because
// EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE defaults to using CGL, and eglSetSwapInterval is broken for this backend
EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE,
EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE,
EGL_NONE};
surface->eglDisplay = eglGetPlatformDisplay(EGL_PLATFORM_ANGLE_ANGLE, (void*)EGL_DEFAULT_DISPLAY, displayAttribs);
eglInitialize(surface->eglDisplay, NULL, NULL);
EGLint const configAttributes[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 24,
EGL_STENCIL_SIZE, 8,
EGL_SAMPLE_BUFFERS, 0,
EGL_SAMPLES, EGL_DONT_CARE,
EGL_COLOR_COMPONENT_TYPE_EXT, EGL_COLOR_COMPONENT_TYPE_FIXED_EXT,
EGL_NONE };
eglChooseConfig(surface->eglDisplay, configAttributes, &surface->eglConfig, 1, &surface->numConfigs);
EGLint const surfaceAttributes[] = {EGL_NONE};
surface->eglSurface = eglCreateWindowSurface(surface->eglDisplay, surface->eglConfig, surface->layer, surfaceAttributes);
eglBindAPI(EGL_OPENGL_ES_API);
EGLint contextAttributes[] = {
EGL_CONTEXT_MAJOR_VERSION_KHR, 3,
EGL_CONTEXT_MINOR_VERSION_KHR, 0, //NOTE: Angle can't create a GLES 3.1 context on macOS
EGL_CONTEXT_BIND_GENERATES_RESOURCE_CHROMIUM, EGL_TRUE,
EGL_CONTEXT_CLIENT_ARRAYS_ENABLED_ANGLE, EGL_TRUE,
EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE, EGL_FALSE,
EGL_NONE};
surface->eglContext = eglCreateContext(surface->eglDisplay, surface->eglConfig, EGL_NO_CONTEXT, contextAttributes);
eglMakeCurrent(surface->eglDisplay, surface->eglSurface, surface->eglSurface, surface->eglContext);
eglSwapInterval(surface->eglDisplay, 1);
mg_surface handle = mg_surface_alloc_handle((mg_surface_data*)surface);
return(handle);
}
mg_surface mg_gles_surface_create_offscreen(u32 width, u32 height)
{
return(mg_gles_surface_create_with_view(width, height, 0));
}
mg_surface mg_gles_surface_create_for_window(mp_window window)
{
mg_surface res = mg_surface_nil();
mp_window_data* windowData = mp_window_ptr_from_handle(window);
if(windowData)
{
@autoreleasepool
{
NSRect frame = [[windowData->osx.nsWindow contentView] frame];
NSView* view = [[NSView alloc] initWithFrame: frame];
res = mg_gles_surface_create_with_view(frame.size.width, frame.size.height, view);
if(!mg_surface_is_nil(res))
{
[[windowData->osx.nsWindow contentView] addSubview: view];
}
}
}
return(res);
}
/************************************************************//**
*
* @file: osx_gles_surface.cpp
* @author: Martin Fouilleul
* @date: 18/08/2022
* @revision:
*
*****************************************************************/
//#include<Cocoa/Cocoa.h>
//#include <Foundation/Foundation.h>
#include <QuartzCore/QuartzCore.h>
#include<GLES3/gl3.h>
#define EGL_EGLEXT_PROTOTYPES
#include<EGL/egl.h>
#include<EGL/eglext.h>
#include"graphics_internal.h"
#include"osx_gles_surface.h"
void mg_gles_surface_destroy(mg_surface_data* interface)
{
//////////////////////////////////////////////////
//TODO
//////////////////////////////////////////////////
}
void mg_gles_surface_prepare(mg_surface_data* interface)
{
mg_gles_surface* surface = (mg_gles_surface*)interface;
eglMakeCurrent(surface->eglDisplay, surface->eglSurface, surface->eglSurface, surface->eglContext);
}
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)
{
mg_gles_surface* surface = (mg_gles_surface*)interface;
f32 scale = surface->layer.contentsScale;
CGRect bounds = (CGRect){{frame.x * scale, frame.y * scale}, {.width = frame.w*scale, .height = frame.h*scale}};
[surface->layer setBounds: bounds];
}
mp_rect mg_gles_surface_get_frame(mg_surface_data* interface)
{
mg_gles_surface* surface = (mg_gles_surface*)interface;
f32 scale = surface->layer.contentsScale;
CGRect bounds = [surface->layer bounds];
mp_rect rect = {bounds.origin.x / scale, bounds.origin.y / scale, bounds.size.width / scale, bounds.size.height / scale};
return(rect);
}
void mg_gles_surface_set_hidden(mg_surface_data* interface, bool hidden)
{
//TODO: doesn't make sense for an offscreen surface?
}
bool mg_gles_surface_get_hidden(mg_surface_data* interface)
{
//TODO: doesn't make sense for an offscreen surface?
return(false);
}
vec2 mg_gles_surface_get_size(mg_surface_data* interface)
{
mg_gles_surface* surface = (mg_gles_surface*)interface;
CGRect bounds = [surface->layer bounds];
f32 scale = surface->layer.contentsScale;
vec2 res = {bounds.size.width/scale, bounds.size.height/scale};
return(res);
}
mg_surface mg_gles_surface_create_with_view(u32 width, u32 height, NSView* view)
{
mg_gles_surface* surface = malloc_type(mg_gles_surface);
memset(surface, 0, sizeof(mg_gles_surface));
surface->interface.backend = MG_BACKEND_GLES;
surface->interface.destroy = mg_gles_surface_destroy;
surface->interface.prepare = mg_gles_surface_prepare;
surface->interface.present = mg_gles_surface_present;
surface->interface.getFrame = mg_gles_surface_get_frame;
surface->interface.setFrame = mg_gles_surface_set_frame;
surface->interface.getHidden = mg_gles_surface_get_hidden;
surface->interface.setHidden = mg_gles_surface_set_hidden;
surface->view = view;
@autoreleasepool
{
surface->layer = [[CALayer alloc] init];
[surface->layer retain];
[surface->layer setBounds:CGRectMake(0, 0, width, height)];
if(surface->view)
{
[surface->view setWantsLayer: YES];
surface->view.layer = surface->layer;
}
}
EGLAttrib displayAttribs[] = {
//NOTE: we need to explicitly set EGL_PLATFORM_ANGLE_TYPE_ANGLE to EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE, because
// EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE defaults to using CGL, and eglSetSwapInterval is broken for this backend
EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE,
EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE,
EGL_NONE};
surface->eglDisplay = eglGetPlatformDisplay(EGL_PLATFORM_ANGLE_ANGLE, (void*)EGL_DEFAULT_DISPLAY, displayAttribs);
eglInitialize(surface->eglDisplay, NULL, NULL);
EGLint const configAttributes[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 24,
EGL_STENCIL_SIZE, 8,
EGL_SAMPLE_BUFFERS, 0,
EGL_SAMPLES, EGL_DONT_CARE,
EGL_COLOR_COMPONENT_TYPE_EXT, EGL_COLOR_COMPONENT_TYPE_FIXED_EXT,
EGL_NONE };
eglChooseConfig(surface->eglDisplay, configAttributes, &surface->eglConfig, 1, &surface->numConfigs);
EGLint const surfaceAttributes[] = {EGL_NONE};
surface->eglSurface = eglCreateWindowSurface(surface->eglDisplay, surface->eglConfig, surface->layer, surfaceAttributes);
eglBindAPI(EGL_OPENGL_ES_API);
EGLint contextAttributes[] = {
EGL_CONTEXT_MAJOR_VERSION_KHR, 3,
EGL_CONTEXT_MINOR_VERSION_KHR, 0, //NOTE: Angle can't create a GLES 3.1 context on macOS
EGL_CONTEXT_BIND_GENERATES_RESOURCE_CHROMIUM, EGL_TRUE,
EGL_CONTEXT_CLIENT_ARRAYS_ENABLED_ANGLE, EGL_TRUE,
EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE, EGL_FALSE,
EGL_NONE};
surface->eglContext = eglCreateContext(surface->eglDisplay, surface->eglConfig, EGL_NO_CONTEXT, contextAttributes);
eglMakeCurrent(surface->eglDisplay, surface->eglSurface, surface->eglSurface, surface->eglContext);
eglSwapInterval(surface->eglDisplay, 1);
mg_surface handle = mg_surface_alloc_handle((mg_surface_data*)surface);
return(handle);
}
mg_surface mg_gles_surface_create_offscreen(u32 width, u32 height)
{
return(mg_gles_surface_create_with_view(width, height, 0));
}
mg_surface mg_gles_surface_create_for_window(mp_window window)
{
mg_surface res = mg_surface_nil();
mp_window_data* windowData = mp_window_ptr_from_handle(window);
if(windowData)
{
@autoreleasepool
{
NSRect frame = [[windowData->osx.nsWindow contentView] frame];
NSView* view = [[NSView alloc] initWithFrame: frame];
res = mg_gles_surface_create_with_view(frame.size.width, frame.size.height, view);
if(!mg_surface_is_nil(res))
{
[[windowData->osx.nsWindow contentView] addSubview: view];
}
}
}
return(res);
}

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
* @revision:
*
*****************************************************************/
#include<GLES3/gl32.h>
#include<GLES3/gl31.h>
#define EGL_EGLEXT_PROTOTYPES
#include<EGL/egl.h>
#include<EGL/eglext.h>
@ -108,7 +108,7 @@ mg_surface mg_gles_surface_create_for_window(mp_window window)
eglBindAPI(EGL_OPENGL_ES_API);
EGLint contextAttributes[] = {
EGL_CONTEXT_MAJOR_VERSION_KHR, 3,
EGL_CONTEXT_MINOR_VERSION_KHR, 0, //NOTE: Angle can't create a GLES 3.1 context on macOS
EGL_CONTEXT_MINOR_VERSION_KHR, 1, //NOTE: Angle can't create a GLES 3.1 context on macOS
EGL_CONTEXT_BIND_GENERATES_RESOURCE_CHROMIUM, EGL_TRUE,
EGL_CONTEXT_CLIENT_ARRAYS_ENABLED_ANGLE, EGL_TRUE,
EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE, EGL_FALSE,

View File

@ -9,6 +9,9 @@
#ifndef __WIN32_GLES_SURFACE_H_
#define __WIN32_GLES_SURFACE_H_
mg_surface mg_gles_surface_create_for_window(mg_window window);
#include"graphics.h"
#include"mp_app.h"
mg_surface mg_gles_surface_create_for_window(mp_window window);
#endif // __WIN32_GLES_SURFACE_H_