Fixed indexing in gles_canvas_fragment shaders and fixed native keys buffer
This commit is contained in:
parent
8e87837fcc
commit
e0300e9e3c
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
7483
src/graphics.c
7483
src/graphics.c
File diff suppressed because it is too large
Load Diff
|
@ -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_
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
//----------------------------------------------------------------
|
||||
|
|
698
src/mp_app.c
698
src/mp_app.c
|
@ -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
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
1448
src/win32_app.c
1448
src/win32_app.c
File diff suppressed because it is too large
Load Diff
|
@ -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,
|
||||
|
|
|
@ -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_
|
||||
|
|
Loading…
Reference in New Issue