trying to integrate mg_surface_client/server
This commit is contained in:
parent
0becc301d0
commit
13cccdf5de
7
build.sh
7
build.sh
|
@ -24,7 +24,7 @@ if [ $OS = "Darwin" ] ; then
|
|||
CXX=clang++
|
||||
DYLIB_SUFFIX='dylib'
|
||||
SYS_LIBS=''
|
||||
FLAGS="-mmacos-version-min=10.15.4 -DMG_IMPLEMENTS_BACKEND_METAL -maes"
|
||||
FLAGS="-mmacos-version-min=10.15.4 -DMG_IMPLEMENTS_BACKEND_METAL -DMG_IMPLEMENTS_BACKEND_GLES -maes"
|
||||
CFLAGS="-std=c11"
|
||||
|
||||
elif [ $OS = "Linux" ] ; then
|
||||
|
@ -40,8 +40,9 @@ fi
|
|||
#--------------------------------------------------------------
|
||||
BINDIR="./bin"
|
||||
SRCDIR="./src"
|
||||
EXTDIR="./ext"
|
||||
RESDIR="./resources"
|
||||
INCLUDES="-I$SRCDIR -I$SRCDIR/util -I$SRCDIR/platform"
|
||||
INCLUDES="-I$SRCDIR -I$SRCDIR/util -I$SRCDIR/platform -I$EXTDIR/angle_headers"
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# Build
|
||||
|
@ -60,7 +61,7 @@ if [ $target = 'lib' ] ; then
|
|||
# compile milepost. We use one compilation unit for all C code, and one compilation
|
||||
# unit for all ObjectiveC code
|
||||
$CC $DEBUG_FLAGS -c -o $BINDIR/milepost_c.o $CFLAGS $FLAGS $INCLUDES $SRCDIR/milepost.c
|
||||
$CC $DEBUG_FLAGS -c -o $BINDIR/milepost_objc.o $FLAGS $INCLUDES $SRCDIR/milepost.mm
|
||||
$CC $DEBUG_FLAGS -c -o $BINDIR/milepost_objc.o $FLAGS $INCLUDES $SRCDIR/milepost.m
|
||||
|
||||
# build the static library
|
||||
libtool -static -o $BINDIR/libmilepost.a $BINDIR/milepost_c.o $BINDIR/milepost_objc.o
|
||||
|
|
355
src/graphics.c
355
src/graphics.c
|
@ -219,26 +219,43 @@ typedef struct mg_font_info
|
|||
// internal handle system
|
||||
//---------------------------------------------------------------
|
||||
|
||||
const u32 MG_MAX_SURFACES = 256,
|
||||
const u32 MG_MAX_RESOURCE_SLOTS = 256,
|
||||
MG_MAX_CONTEXTS = 256,
|
||||
MG_FONT_MAX_COUNT = 256;
|
||||
|
||||
typedef struct mg_surface_slot
|
||||
typedef struct mg_resource_slot
|
||||
{
|
||||
list_elt freeListElt;
|
||||
u32 generation;
|
||||
union
|
||||
{
|
||||
mg_surface_info* surface;
|
||||
mg_surface_server_info* server;
|
||||
mg_surface_client_info* client;
|
||||
};
|
||||
|
||||
} mg_surface_slot;
|
||||
} mg_resource_slot;
|
||||
|
||||
typedef struct mg_resource_pool
|
||||
{
|
||||
mg_resource_slot slots[MG_MAX_RESOURCE_SLOTS];
|
||||
list_info freeList;
|
||||
u32 nextIndex;
|
||||
|
||||
} mg_resource_pool;
|
||||
|
||||
typedef struct mg_info
|
||||
{
|
||||
bool init;
|
||||
list_info surfaceFreeList;
|
||||
|
||||
mg_resource_pool surfaces;
|
||||
mg_resource_pool servers;
|
||||
mg_resource_pool clients;
|
||||
|
||||
list_info contextFreeList;
|
||||
list_info fontFreeList;
|
||||
u32 fontsNextIndex;
|
||||
mg_surface_slot surfaceSlots[MG_MAX_SURFACES];
|
||||
|
||||
mg_canvas_data contexts[MG_MAX_CONTEXTS];
|
||||
mg_font_info fonts[MG_FONT_MAX_COUNT];
|
||||
|
||||
|
@ -246,16 +263,14 @@ typedef struct mg_info
|
|||
|
||||
static mg_info __mgInfo = {0};
|
||||
|
||||
|
||||
void mg_init()
|
||||
{
|
||||
if(!__mgInfo.init)
|
||||
{
|
||||
ListInit(&__mgInfo.surfaceFreeList);
|
||||
for(int i=0; i<MG_MAX_SURFACES; i++)
|
||||
{
|
||||
__mgInfo.surfaceSlots[i].generation = 1;
|
||||
ListAppend(&__mgInfo.surfaceFreeList, &__mgInfo.surfaceSlots[i].freeListElt);
|
||||
}
|
||||
__mgInfo.init = true;
|
||||
|
||||
//TODO: make context handles suitable for zero init
|
||||
|
||||
ListInit(&__mgInfo.contextFreeList);
|
||||
for(int i=0; i<MG_MAX_CONTEXTS; i++)
|
||||
|
@ -263,18 +278,24 @@ void mg_init()
|
|||
__mgInfo.contexts[i].generation = 1;
|
||||
ListAppend(&__mgInfo.contextFreeList, &__mgInfo.contexts[i].freeListElt);
|
||||
}
|
||||
|
||||
__mgInfo.init = true;
|
||||
}
|
||||
}
|
||||
|
||||
mg_surface_slot* mg_surface_slot_alloc()
|
||||
mg_resource_slot* mg_resource_slot_alloc(mg_resource_pool* pool)
|
||||
{
|
||||
return(ListPopEntry(&__mgInfo.surfaceFreeList, mg_surface_slot, freeListElt));
|
||||
mg_resource_slot* slot = ListPopEntry(&pool->freeList, mg_resource_slot, freeListElt);
|
||||
if(!slot && pool->nextIndex < MG_MAX_RESOURCE_SLOTS)
|
||||
{
|
||||
slot = &pool->slots[pool->nextIndex];
|
||||
slot->generation = 1;
|
||||
pool->nextIndex++;
|
||||
}
|
||||
return(slot);
|
||||
}
|
||||
|
||||
void mg_surface_slot_recycle(mg_surface_slot* slot)
|
||||
void mg_resource_slot_recycle(mg_resource_pool* pool, mg_resource_slot* slot)
|
||||
{
|
||||
DEBUG_ASSERT(slot >= pool->slots && slot < pool->slots + MG_MAX_RESOURCE_SLOTS);
|
||||
#ifdef DEBUG
|
||||
if(slot->generation == UINT32_MAX)
|
||||
{
|
||||
|
@ -282,18 +303,18 @@ void mg_surface_slot_recycle(mg_surface_slot* slot)
|
|||
}
|
||||
#endif
|
||||
slot->generation++;
|
||||
ListPush(&__mgInfo.surfaceFreeList, &slot->freeListElt);
|
||||
ListPush(&pool->freeList, &slot->freeListElt);
|
||||
}
|
||||
|
||||
mg_surface_slot* mg_surface_slot_from_handle(mg_surface surface)
|
||||
mg_resource_slot* mg_resource_slot_from_handle(mg_resource_pool* pool, u64 h)
|
||||
{
|
||||
u32 index = surface.h>>32;
|
||||
u32 generation = surface.h & 0xffffffff;
|
||||
if(index >= MG_MAX_SURFACES)
|
||||
u32 index = h>>32;
|
||||
u32 generation = h & 0xffffffff;
|
||||
if(index >= MG_MAX_RESOURCE_SLOTS)
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
mg_surface_slot* slot = &__mgInfo.surfaceSlots[index];
|
||||
mg_resource_slot* slot = &pool->slots[index];
|
||||
if(slot->generation != generation)
|
||||
{
|
||||
return(0);
|
||||
|
@ -304,9 +325,42 @@ mg_surface_slot* mg_surface_slot_from_handle(mg_surface surface)
|
|||
}
|
||||
}
|
||||
|
||||
u64 mg_resource_handle_from_slot(mg_resource_pool* pool, mg_resource_slot* slot)
|
||||
{
|
||||
DEBUG_ASSERT( (slot - pool->slots) >= 0
|
||||
&& (slot - pool->slots) < MG_MAX_RESOURCE_SLOTS);
|
||||
|
||||
u64 h = ((u64)(slot - pool->slots))<<32
|
||||
|((u64)(slot->generation));
|
||||
return(h);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// surface handles
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
mg_surface mg_surface_nil()
|
||||
{
|
||||
return((mg_surface){.h = 0});
|
||||
}
|
||||
|
||||
mg_surface mg_surface_alloc_handle(mg_surface_info* surface)
|
||||
{
|
||||
mg_resource_slot* slot = mg_resource_slot_alloc(&__mgInfo.surfaces);
|
||||
if(!slot)
|
||||
{
|
||||
LOG_ERROR("no more surface slots\n");
|
||||
return(mg_surface_nil());
|
||||
}
|
||||
slot->surface = surface;
|
||||
u64 h = mg_resource_handle_from_slot(&__mgInfo.surfaces, slot);
|
||||
mg_surface handle = {h};
|
||||
return(handle);
|
||||
}
|
||||
|
||||
mg_surface_info* mg_surface_ptr_from_handle(mg_surface surface)
|
||||
{
|
||||
mg_surface_slot* slot = mg_surface_slot_from_handle(surface);
|
||||
mg_resource_slot* slot = mg_resource_slot_from_handle(&__mgInfo.surfaces, surface.h);
|
||||
if(slot)
|
||||
{
|
||||
return(slot->surface);
|
||||
|
@ -317,29 +371,80 @@ mg_surface_info* mg_surface_ptr_from_handle(mg_surface surface)
|
|||
}
|
||||
}
|
||||
|
||||
mg_surface mg_surface_handle_from_slot(mg_surface_slot* slot)
|
||||
{
|
||||
DEBUG_ASSERT( (slot - __mgInfo.surfaceSlots) >= 0
|
||||
&& (slot - __mgInfo.surfaceSlots) < MG_MAX_SURFACES);
|
||||
//------------------------------------------------------------------------
|
||||
// surface server handles
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
u64 h = ((u64)(slot - __mgInfo.surfaceSlots))<<32
|
||||
|((u64)(slot->generation));
|
||||
return((mg_surface){.h = h});
|
||||
mg_surface_server mg_surface_server_nil()
|
||||
{
|
||||
return((mg_surface_server){.h = 0});
|
||||
}
|
||||
|
||||
mg_surface mg_surface_alloc_handle(mg_surface_info* surface)
|
||||
mg_surface_server mg_surface_server_alloc_handle(mg_surface_server_info* server)
|
||||
{
|
||||
mg_surface_slot* slot = mg_surface_slot_alloc();
|
||||
slot->surface = surface;
|
||||
mg_surface handle = mg_surface_handle_from_slot(slot);
|
||||
mg_resource_slot* slot = mg_resource_slot_alloc(&__mgInfo.servers);
|
||||
if(!slot)
|
||||
{
|
||||
LOG_ERROR("no more server slots\n");
|
||||
return(mg_surface_server_nil());
|
||||
}
|
||||
slot->server = server;
|
||||
u64 h = mg_resource_handle_from_slot(&__mgInfo.servers, slot);
|
||||
mg_surface_server handle = {h};
|
||||
return(handle);
|
||||
}
|
||||
|
||||
mg_surface mg_surface_nil()
|
||||
mg_surface_server_info* mg_surface_server_ptr_from_handle(mg_surface_server server)
|
||||
{
|
||||
return((mg_surface){.h = 0});
|
||||
mg_resource_slot* slot = mg_resource_slot_from_handle(&__mgInfo.servers, server.h);
|
||||
if(slot)
|
||||
{
|
||||
return(slot->server);
|
||||
}
|
||||
else
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// surface client handles
|
||||
//------------------------------------------------------------------------
|
||||
mg_surface_client mg_surface_client_nil()
|
||||
{
|
||||
return((mg_surface_client){.h = 0});
|
||||
}
|
||||
|
||||
mg_surface_client mg_surface_client_alloc_handle(mg_surface_client_info* client)
|
||||
{
|
||||
mg_resource_slot* slot = mg_resource_slot_alloc(&__mgInfo.clients);
|
||||
if(!slot)
|
||||
{
|
||||
LOG_ERROR("no more client slots\n");
|
||||
return(mg_surface_client_nil());
|
||||
}
|
||||
slot->client = client;
|
||||
u64 h = mg_resource_handle_from_slot(&__mgInfo.clients, slot);
|
||||
mg_surface_client handle = {h};
|
||||
return(handle);
|
||||
}
|
||||
|
||||
mg_surface_client_info* mg_surface_client_ptr_from_handle(mg_surface_client client)
|
||||
{
|
||||
mg_resource_slot* slot = mg_resource_slot_from_handle(&__mgInfo.clients, client.h);
|
||||
if(slot)
|
||||
{
|
||||
return(slot->client);
|
||||
}
|
||||
else
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// canvas
|
||||
//------------------------------------------------------------------------
|
||||
mg_canvas_data* mg_canvas_alloc()
|
||||
{
|
||||
return(ListPopEntry(&__mgInfo.contextFreeList, mg_canvas_data, freeListElt));
|
||||
|
@ -421,6 +526,11 @@ mg_surface mg_metal_surface_create_for_window(mp_window window);
|
|||
mg_surface mg_metal_surface_create_for_view(mp_view view);
|
||||
#endif //MG_IMPLEMENTS_BACKEND_METAL
|
||||
|
||||
#ifdef MG_IMPLEMENTS_BACKEND_GLES
|
||||
mg_surface mg_gles_surface_create_offscreen();
|
||||
mg_surface_server mg_gles_surface_create_server(mg_surface_info* surface);
|
||||
#endif //MG_IMPLEMENTS_BACKEND_GLES
|
||||
|
||||
void mg_init();
|
||||
|
||||
mg_surface mg_surface_create_for_window(mp_window window, mg_backend_id backend)
|
||||
|
@ -469,16 +579,51 @@ mg_surface mg_surface_create_for_view(mp_view view, mg_backend_id backend)
|
|||
return(surface);
|
||||
}
|
||||
|
||||
mg_surface mg_surface_create_offscreen(mg_backend_id backend)
|
||||
{
|
||||
DEBUG_ASSERT(__mgInfo.init);
|
||||
|
||||
mg_surface surface = mg_surface_nil();
|
||||
|
||||
switch(backend)
|
||||
{
|
||||
#ifdef MG_IMPLEMENTS_BACKEND_GLES
|
||||
case MG_BACKEND_GLES:
|
||||
surface = mg_gles_surface_create_offscreen();
|
||||
break;
|
||||
#endif
|
||||
|
||||
//...
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return(surface);
|
||||
}
|
||||
|
||||
void* mg_surface_get_os_resource(mg_surface surface)
|
||||
{
|
||||
DEBUG_ASSERT(__mgInfo.init);
|
||||
|
||||
void* res = 0;
|
||||
mg_resource_slot* slot = mg_resource_slot_from_handle(&__mgInfo.surfaces, surface.h);
|
||||
if(slot)
|
||||
{
|
||||
res = slot->surface->getOSResource(slot->surface);
|
||||
}
|
||||
return(res);
|
||||
}
|
||||
|
||||
void mg_surface_destroy(mg_surface handle)
|
||||
{
|
||||
DEBUG_ASSERT(__mgInfo.init);
|
||||
|
||||
mg_surface_slot* slot = mg_surface_slot_from_handle(handle);
|
||||
mg_resource_slot* slot = mg_resource_slot_from_handle(&__mgInfo.surfaces, handle.h);
|
||||
if(slot)
|
||||
{
|
||||
slot->surface->destroy(slot->surface);
|
||||
mg_surface_slot_recycle(slot);
|
||||
mg_resource_slot_recycle(&__mgInfo.surfaces, slot);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -515,6 +660,18 @@ void mg_surface_present(mg_surface surface)
|
|||
}
|
||||
}
|
||||
|
||||
void mg_surface_set_hidden(mg_surface surface, bool hidden)
|
||||
{
|
||||
DEBUG_ASSERT(__mgInfo.init);
|
||||
|
||||
mg_surface_info* surfaceInfo = mg_surface_ptr_from_handle(surface);
|
||||
if(surfaceInfo)
|
||||
{
|
||||
surfaceInfo->setHidden(surfaceInfo, hidden);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
vec2 mg_surface_size(mg_surface surface)
|
||||
{
|
||||
DEBUG_ASSERT(__mgInfo.init);
|
||||
|
@ -527,6 +684,97 @@ vec2 mg_surface_size(mg_surface surface)
|
|||
return(res);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// graphics surface server
|
||||
//---------------------------------------------------------------
|
||||
|
||||
mg_surface_server mg_surface_server_create(mg_surface surface)
|
||||
{
|
||||
mg_surface_server server = mg_surface_server_nil();
|
||||
|
||||
mg_surface_info* surfaceInfo = mg_surface_ptr_from_handle(surface);
|
||||
if(surfaceInfo)
|
||||
{
|
||||
switch(surfaceInfo->backend)
|
||||
{
|
||||
case MG_BACKEND_GLES:
|
||||
server = mg_gles_surface_create_server(surfaceInfo);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return(server);
|
||||
}
|
||||
|
||||
void mg_surface_server_destroy(mg_surface_server handle)
|
||||
{
|
||||
mg_resource_slot* slot = mg_resource_slot_from_handle(&__mgInfo.servers, handle.h);
|
||||
if(slot)
|
||||
{
|
||||
slot->server->destroy(slot->server);
|
||||
mg_resource_slot_recycle(&__mgInfo.servers, slot);
|
||||
}
|
||||
}
|
||||
|
||||
mg_surface_server_id mg_surface_server_get_id(mg_surface_server server)
|
||||
{
|
||||
mg_surface_server_info* serverInfo = mg_surface_server_ptr_from_handle(server);
|
||||
if(serverInfo)
|
||||
{
|
||||
return(serverInfo->getID(serverInfo));
|
||||
}
|
||||
else
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// graphics surface client
|
||||
//---------------------------------------------------------------
|
||||
|
||||
//TODO: move elsewhere, guard with OS ifdef
|
||||
mg_surface_client mg_osx_surface_client_create(mg_surface_server_id id);
|
||||
|
||||
mg_surface_client mg_surface_client_create(mg_surface_server_id id)
|
||||
{
|
||||
mg_surface_client client = mg_surface_client_nil();
|
||||
|
||||
client = mg_osx_surface_client_create(id);
|
||||
return(client);
|
||||
}
|
||||
|
||||
void mg_surface_client_destroy(mg_surface_client handle)
|
||||
{
|
||||
mg_resource_slot* slot = mg_resource_slot_from_handle(&__mgInfo.clients, handle.h);
|
||||
if(slot)
|
||||
{
|
||||
slot->client->destroy(slot->client);
|
||||
mg_resource_slot_recycle(&__mgInfo.clients, slot);
|
||||
}
|
||||
}
|
||||
|
||||
void mg_surface_client_attach_to_view(mg_surface_client client, mp_view view)
|
||||
{
|
||||
mg_surface_client_info* clientInfo = mg_surface_client_ptr_from_handle(client);
|
||||
|
||||
if(clientInfo)
|
||||
{
|
||||
clientInfo->attachment = view;
|
||||
clientInfo->attach(clientInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void mg_surface_client_detach(mg_surface_client client)
|
||||
{
|
||||
mg_surface_client_info* clientInfo = mg_surface_client_ptr_from_handle(client);
|
||||
if(clientInfo)
|
||||
{
|
||||
clientInfo->detach(clientInfo);
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// graphics stream handles
|
||||
|
@ -3606,15 +3854,14 @@ void mg_clear(mg_canvas handle)
|
|||
mg_push_command(context, (mg_primitive){.cmd = MG_CMD_CLEAR, .attributes = context->attributes});
|
||||
}
|
||||
|
||||
void mg_get_current_position(mg_canvas handle, f32* x, f32* y)
|
||||
vec2 mg_get_position(mg_canvas handle)
|
||||
{
|
||||
mg_canvas_data* context = mg_canvas_ptr_from_handle(handle);
|
||||
if(!context)
|
||||
{
|
||||
return;
|
||||
return((vec2){0, 0});
|
||||
}
|
||||
*x = context->subPathLastPoint.x;
|
||||
*y = context->subPathLastPoint.y;
|
||||
return(context->subPathLastPoint);
|
||||
}
|
||||
|
||||
void mg_path_push_elements(mg_canvas_data* context, u32 count, mg_path_elt* elements)
|
||||
|
@ -4004,6 +4251,11 @@ void mg_arc(mg_canvas handle, f32 x, f32 y, f32 r, f32 arcAngle, f32 startAngle)
|
|||
|
||||
mg_image mg_image_nil() { return((mg_image){0}); }
|
||||
|
||||
bool mg_image_equal(mg_image a, mg_image b)
|
||||
{
|
||||
return(a.h == b.h);
|
||||
}
|
||||
|
||||
mg_image mg_image_handle_from_ptr(mg_canvas_data* canvas, mg_image_data* imageData)
|
||||
{
|
||||
DEBUG_ASSERT( (imageData - canvas->images) >= 0
|
||||
|
@ -4114,6 +4366,21 @@ void mg_image_destroy(mg_canvas handle, mg_image image)
|
|||
//TODO invalidate image handle, maybe free atlas area
|
||||
}
|
||||
|
||||
vec2 mg_image_size(mg_canvas handle, mg_image image)
|
||||
{
|
||||
vec2 size = {0};
|
||||
mg_canvas_data* context = mg_canvas_ptr_from_handle(handle);
|
||||
if(context)
|
||||
{
|
||||
mg_image_data* imageData = mg_image_ptr_from_handle(context, image);
|
||||
if(imageData)
|
||||
{
|
||||
size = (vec2){imageData->rect.w, imageData->rect.h};
|
||||
}
|
||||
}
|
||||
return(size);
|
||||
}
|
||||
|
||||
void mg_image_draw(mg_canvas handle, mg_image image, mp_rect rect)
|
||||
{
|
||||
mg_canvas_data* context = mg_canvas_ptr_from_handle(handle);
|
||||
|
@ -4122,7 +4389,7 @@ void mg_image_draw(mg_canvas handle, mg_image image, mp_rect rect)
|
|||
return;
|
||||
}
|
||||
mg_primitive primitive = {.cmd = MG_CMD_IMAGE_DRAW,
|
||||
.rect = (mp_rect){rect.x, rect.y, rect.h, rect.w},
|
||||
.rect = (mp_rect){rect.x, rect.y, rect.w, rect.h},
|
||||
.attributes = context->attributes};
|
||||
primitive.attributes.image = image;
|
||||
|
||||
|
@ -4137,7 +4404,7 @@ void mg_rounded_image_draw(mg_canvas handle, mg_image image, mp_rect rect, f32 r
|
|||
return;
|
||||
}
|
||||
mg_primitive primitive = {.cmd = MG_CMD_ROUNDED_IMAGE_DRAW,
|
||||
.roundedRect = {rect.x, rect.y, rect.h, rect.w, roundness},
|
||||
.roundedRect = {rect.x, rect.y, rect.w, rect.h, roundness},
|
||||
.attributes = context->attributes};
|
||||
primitive.attributes.image = image;
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ typedef struct mg_surface { u64 h; } mg_surface;
|
|||
|
||||
typedef enum { MG_BACKEND_DUMMY,
|
||||
MG_BACKEND_METAL,
|
||||
MG_BACKEND_GLES,
|
||||
//...
|
||||
} mg_backend_id;
|
||||
|
||||
|
@ -31,14 +32,35 @@ void mg_init();
|
|||
mg_surface mg_surface_nil();
|
||||
mg_surface mg_surface_create_for_window(mp_window window, mg_backend_id backend);
|
||||
mg_surface mg_surface_create_for_view(mp_view view, mg_backend_id backend);
|
||||
mg_surface mg_surface_create_offscreen(mg_backend_id backend);
|
||||
|
||||
void mg_surface_destroy(mg_surface surface);
|
||||
void* mg_surface_get_os_resource(mg_surface surface);
|
||||
|
||||
void mg_surface_prepare(mg_surface surface);
|
||||
void mg_surface_present(mg_surface surface);
|
||||
void mg_surface_resize(mg_surface surface, int width, int height);
|
||||
void mg_surface_set_hidden(mg_surface surface, bool hidden);
|
||||
|
||||
vec2 mg_surface_size(mg_surface surface);
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
//NOTE(martin): graphics surface sharing
|
||||
//------------------------------------------------------------------------------------------
|
||||
typedef void* mg_surface_server_id;
|
||||
|
||||
typedef struct mg_surface_server { u64 h; } mg_surface_server;
|
||||
typedef struct mg_surface_client { u64 h; } mg_surface_client;
|
||||
|
||||
mg_surface_server mg_surface_server_create(mg_surface surface);
|
||||
void mg_surface_server_destroy(mg_surface_server server);
|
||||
mg_surface_server_id mg_surface_server_get_id(mg_surface_server server);
|
||||
|
||||
mg_surface_client mg_surface_client_create(mg_surface_server_id id);
|
||||
void mg_surface_client_destroy(mg_surface_client client);
|
||||
void mg_surface_client_attach_to_view(mg_surface_client client, mp_view view);
|
||||
void mg_surface_client_detach(mg_surface_client client);
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
//NOTE(martin): canvas drawing structs
|
||||
//------------------------------------------------------------------------------------------
|
||||
|
@ -181,7 +203,7 @@ bool mg_get_text_flip(mg_canvas context);
|
|||
//------------------------------------------------------------------------------------------
|
||||
//NOTE(martin): path construction
|
||||
//------------------------------------------------------------------------------------------
|
||||
void mg_get_current_position(mg_canvas context, f32* x, f32* y);
|
||||
vec2 mg_get_position(mg_canvas context);
|
||||
void mg_move_to(mg_canvas context, f32 x, f32 y);
|
||||
void mg_line_to(mg_canvas context, f32 x, f32 y);
|
||||
void mg_quadratic_to(mg_canvas context, f32 x1, f32 y1, f32 x2, f32 y2);
|
||||
|
@ -217,12 +239,16 @@ void mg_arc(mg_canvas handle, f32 x, f32 y, f32 r, f32 arcAngle, f32 startAngle)
|
|||
//------------------------------------------------------------------------------------------
|
||||
typedef struct mg_image { u64 h; } mg_image;
|
||||
|
||||
mg_image mg_image_nil();
|
||||
bool mg_image_equal(mg_image a, mg_image b);
|
||||
|
||||
mg_image mg_image_create_from_rgba8(mg_canvas canvas, u32 width, u32 height, u8* pixels);
|
||||
mg_image mg_image_create_from_data(mg_canvas canvas, str8 data, bool flip);
|
||||
mg_image mg_image_create_from_file(mg_canvas canvas, str8 path, bool flip);
|
||||
|
||||
void mg_image_drestroy(mg_canvas canvas, mg_image image);
|
||||
|
||||
vec2 mg_image_size(mg_canvas canvas, mg_image image);
|
||||
void mg_image_draw(mg_canvas canvas, mg_image image, mp_rect rect);
|
||||
void mg_rounded_image_draw(mg_canvas handle, mg_image image, mp_rect rect, f32 roundness);
|
||||
|
||||
|
|
|
@ -15,13 +15,18 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
// Surfaces
|
||||
//---------------------------------------------------------------------------------------------
|
||||
typedef struct mg_surface_info mg_surface_info;
|
||||
|
||||
typedef void (*mg_surface_prepare_proc)(mg_surface_info* surface);
|
||||
typedef void (*mg_surface_present_proc)(mg_surface_info* surface);
|
||||
typedef void (*mg_surface_resize_proc)(mg_surface_info* surface, u32 width, u32 height);
|
||||
typedef void (*mg_surface_set_hidden_proc)(mg_surface_info* surface, bool hidden);
|
||||
typedef vec2 (*mg_surface_get_size_proc)(mg_surface_info* surface);
|
||||
typedef void (*mg_surface_destroy_proc)(mg_surface_info* surface);
|
||||
typedef void* (*mg_surface_get_os_resource_proc)(mg_surface_info* surface);
|
||||
|
||||
typedef struct mg_surface_info
|
||||
{
|
||||
|
@ -30,12 +35,54 @@ typedef struct mg_surface_info
|
|||
mg_surface_prepare_proc prepare;
|
||||
mg_surface_present_proc present;
|
||||
mg_surface_resize_proc resize;
|
||||
mg_surface_set_hidden_proc setHidden;
|
||||
mg_surface_get_size_proc getSize;
|
||||
mg_surface_get_os_resource_proc getOSResource;
|
||||
|
||||
} mg_surface_info;
|
||||
|
||||
mg_surface mg_surface_alloc_handle(mg_surface_info* surface);
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
// Surface servers
|
||||
//---------------------------------------------------------------------------------------------
|
||||
typedef struct mg_surface_server_info mg_surface_server_info;
|
||||
|
||||
typedef void (*mg_surface_server_destroy_proc)(mg_surface_server_info* server);
|
||||
typedef mg_surface_server_id (*mg_surface_server_get_id_proc)(mg_surface_server_info* server);
|
||||
|
||||
typedef struct mg_surface_server_info
|
||||
{
|
||||
mg_surface_server_destroy_proc destroy;
|
||||
mg_surface_server_get_id_proc getID;
|
||||
|
||||
} mg_surface_server_info;
|
||||
|
||||
mg_surface_server mg_surface_server_alloc_handle(mg_surface_server_info* server);
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
// Surface clients
|
||||
//---------------------------------------------------------------------------------------------
|
||||
typedef struct mg_surface_client_info mg_surface_client_info;
|
||||
|
||||
typedef void (*mg_surface_client_destroy_proc)(mg_surface_client_info* client);
|
||||
typedef void (*mg_surface_client_attach_proc)(mg_surface_client_info* client);
|
||||
typedef void (*mg_surface_client_detach_proc)(mg_surface_client_info* client);
|
||||
|
||||
typedef struct mg_surface_client_info
|
||||
{
|
||||
mg_surface_client_destroy_proc destroy;
|
||||
mg_surface_client_attach_proc attach;
|
||||
mg_surface_client_detach_proc detach;
|
||||
|
||||
mp_view attachment;
|
||||
|
||||
} mg_surface_client_info;
|
||||
|
||||
mg_surface_client mg_surface_client_alloc_handle(mg_surface_client_info* client);
|
||||
//---------------------------------------------------------------------------------------------
|
||||
// vertex layout
|
||||
//---------------------------------------------------------------------------------------------
|
||||
typedef struct mgc_vertex_layout
|
||||
{
|
||||
u32 maxVertexCount;
|
||||
|
|
|
@ -1,419 +0,0 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: graphics.mm
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 12/07/2020
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#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_PAINTER_DEFAULT_BUFFER_LENGTH = 4<<20;
|
||||
|
||||
typedef struct mg_metal_painter
|
||||
{
|
||||
mg_canvas_painter interface;
|
||||
|
||||
mg_metal_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_painter;
|
||||
|
||||
void mg_metal_painter_draw_buffers(mg_canvas_painter* interface, u32 vertexCount, u32 indexCount, mg_color clearColor)
|
||||
{
|
||||
mg_metal_painter* painter = (mg_metal_painter*)interface;
|
||||
mg_metal_surface* surface = painter->surface;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
if(surface->commandBuffer == nil || surface->commandBuffer == nil)
|
||||
{
|
||||
mg_metal_surface_acquire_drawable_and_command_buffer(surface);
|
||||
}
|
||||
|
||||
ASSERT(indexCount * sizeof(i32) < [painter->indexBuffer length]);
|
||||
|
||||
f32 scale = surface->metalLayer.contentsScale;
|
||||
vector_uint2 viewportSize = {painter->viewPort.w * scale, painter->viewPort.h * scale};
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): encode the clear counter
|
||||
//-----------------------------------------------------------
|
||||
id<MTLBlitCommandEncoder> blitEncoder = [surface->commandBuffer blitCommandEncoder];
|
||||
[blitEncoder fillBuffer: painter->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: painter->boxingPipeline];
|
||||
[boxEncoder setBuffer: painter->vertexBuffer offset:0 atIndex: 0];
|
||||
[boxEncoder setBuffer: painter->indexBuffer offset:0 atIndex: 1];
|
||||
[boxEncoder setBuffer: painter->triangleArray offset:0 atIndex: 2];
|
||||
[boxEncoder setBuffer: painter->boxArray offset:0 atIndex: 3];
|
||||
[boxEncoder setBytes: &scale length: sizeof(float) atIndex: 4];
|
||||
|
||||
MTLSize boxGroupSize = MTLSizeMake(painter->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: painter->tilingPipeline];
|
||||
[tileEncoder setBuffer: painter->boxArray offset:0 atIndex: 0];
|
||||
[tileEncoder setBuffer: painter->tileCounters offset:0 atIndex: 1];
|
||||
[tileEncoder setBuffer: painter->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: painter->sortingPipeline];
|
||||
[sortEncoder setBuffer: painter->tileCounters offset:0 atIndex: 0];
|
||||
[sortEncoder setBuffer: painter->triangleArray offset:0 atIndex: 1];
|
||||
[sortEncoder setBuffer: painter->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(painter->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:painter->computePipeline];
|
||||
[encoder setTexture: painter->outTexture atIndex: 0];
|
||||
[encoder setTexture: painter->atlasTexture atIndex: 1];
|
||||
[encoder setBuffer: painter->vertexBuffer offset:0 atIndex: 0];
|
||||
[encoder setBuffer: painter->tileCounters offset:0 atIndex: 1];
|
||||
[encoder setBuffer: painter->tilesArray offset:0 atIndex: 2];
|
||||
[encoder setBuffer: painter->triangleArray offset:0 atIndex: 3];
|
||||
[encoder setBuffer: painter->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 <= painter->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 = {painter->viewPort.x * scale,
|
||||
painter->viewPort.y * scale,
|
||||
painter->viewPort.w * scale,
|
||||
painter->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: painter->renderPipeline];
|
||||
[renderEncoder setFragmentTexture: painter->outTexture atIndex: 0];
|
||||
[renderEncoder drawPrimitives: MTLPrimitiveTypeTriangle
|
||||
vertexStart: 0
|
||||
vertexCount: 3 ];
|
||||
[renderEncoder endEncoding];
|
||||
}
|
||||
}
|
||||
|
||||
void mg_metal_painter_viewport(mg_canvas_painter* interface, mp_rect viewPort)
|
||||
{
|
||||
mg_metal_painter* painter = (mg_metal_painter*)interface;
|
||||
mg_metal_surface* surface = painter->surface;
|
||||
|
||||
painter->viewPort = viewPort;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
f32 scale = surface->metalLayer.contentsScale;
|
||||
CGSize drawableSize = (CGSize){.width = viewPort.w * scale, .height = viewPort.h * scale};
|
||||
|
||||
[painter->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;
|
||||
|
||||
painter->outTexture = [surface->device newTextureWithDescriptor:texDesc];
|
||||
}
|
||||
}
|
||||
|
||||
void mg_metal_painter_update_vertex_layout(mg_metal_painter* painter)
|
||||
{
|
||||
mg_metal_surface* surface = painter->surface;
|
||||
|
||||
char* vertexBase = (char*)[painter->vertexBuffer contents];
|
||||
|
||||
painter->interface.vertexLayout = (mgc_vertex_layout){
|
||||
.maxVertexCount = MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH,
|
||||
.maxIndexCount = MG_METAL_PAINTER_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 = [painter->indexBuffer contents],
|
||||
.indexStride = sizeof(int)};
|
||||
}
|
||||
|
||||
|
||||
void mg_metal_painter_destroy(mg_canvas_painter* interface)
|
||||
{
|
||||
mg_metal_painter* painter = (mg_metal_painter*)interface;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
[painter->outTexture release];
|
||||
[painter->atlasTexture release];
|
||||
[painter->vertexBuffer release];
|
||||
[painter->indexBuffer release];
|
||||
[painter->tilesArray release];
|
||||
[painter->triangleArray release];
|
||||
[painter->boxArray release];
|
||||
[painter->computePipeline release];
|
||||
}
|
||||
}
|
||||
|
||||
void mg_metal_painter_atlas_upload(mg_canvas_painter* interface, mp_rect rect, u8* bytes)
|
||||
{@autoreleasepool{
|
||||
mg_metal_painter* painter = (mg_metal_painter*)interface;
|
||||
|
||||
MTLRegion region = MTLRegionMake2D(rect.x, rect.y, rect.w, rect.h);
|
||||
[painter->atlasTexture replaceRegion:region
|
||||
mipmapLevel:0
|
||||
withBytes:(void*)bytes
|
||||
bytesPerRow: 4 * rect.w];
|
||||
}}
|
||||
|
||||
extern "C" mg_canvas_painter* mg_metal_painter_create_for_surface(mg_metal_surface* surface, mp_rect viewPort)
|
||||
{
|
||||
mg_metal_painter* painter = malloc_type(mg_metal_painter);
|
||||
painter->surface = surface;
|
||||
|
||||
//NOTE(martin): setup interface functions
|
||||
painter->interface.drawBuffers = mg_metal_painter_draw_buffers;
|
||||
painter->interface.setViewPort = mg_metal_painter_viewport;
|
||||
painter->interface.destroy = mg_metal_painter_destroy;
|
||||
painter->interface.atlasUpload = mg_metal_painter_atlas_upload;
|
||||
|
||||
painter->viewPort = viewPort;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
f32 scale = surface->metalLayer.contentsScale;
|
||||
CGSize drawableSize = (CGSize){.width = viewPort.w * scale, .height = 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;
|
||||
|
||||
painter->outTexture = [surface->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;
|
||||
|
||||
painter->atlasTexture = [surface->device newTextureWithDescriptor:texDesc];
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): create buffers for vertex and index
|
||||
//-----------------------------------------------------------
|
||||
|
||||
MTLResourceOptions bufferOptions = MTLResourceCPUCacheModeWriteCombined
|
||||
| MTLResourceStorageModeShared;
|
||||
|
||||
painter->indexBuffer = [surface->device newBufferWithLength: MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH*sizeof(int)
|
||||
options: bufferOptions];
|
||||
|
||||
painter->vertexBuffer = [surface->device newBufferWithLength: MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH*sizeof(mg_vertex)
|
||||
options: bufferOptions];
|
||||
|
||||
painter->tilesArray = [surface->device newBufferWithLength: RENDERER_TILE_BUFFER_SIZE*sizeof(int)*RENDERER_MAX_TILES
|
||||
options: MTLResourceStorageModePrivate];
|
||||
|
||||
painter->triangleArray = [surface->device newBufferWithLength: MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH*sizeof(mg_triangle_data)
|
||||
options: MTLResourceStorageModePrivate];
|
||||
|
||||
painter->boxArray = [surface->device newBufferWithLength: MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH*sizeof(vector_float4)
|
||||
options: MTLResourceStorageModePrivate];
|
||||
|
||||
//TODO(martin): retain ?
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): create and initialize tile counters
|
||||
//-----------------------------------------------------------
|
||||
painter->tileCounters = [surface->device newBufferWithLength: RENDERER_MAX_TILES*sizeof(uint)
|
||||
options: MTLResourceStorageModePrivate];
|
||||
id<MTLCommandBuffer> commandBuffer = [surface->commandQueue commandBuffer];
|
||||
id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];
|
||||
[blitEncoder fillBuffer: painter->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
|
||||
char* shaderPath = 0;
|
||||
mp_app_get_resource_path("../resources/metal_shader.metallib", &shaderPath);
|
||||
NSString* metalFileName = [[NSString alloc] initWithCString: shaderPath encoding: NSUTF8StringEncoding];
|
||||
free(shaderPath);
|
||||
NSError* err = 0;
|
||||
id<MTLLibrary> library = [surface->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;
|
||||
painter->computePipeline = [surface->device newComputePipelineStateWithFunction: computeFunction
|
||||
error:&error];
|
||||
ASSERT(painter->computePipeline);
|
||||
|
||||
MTLComputePipelineDescriptor* tilingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init];
|
||||
tilingPipelineDesc.computeFunction = tilingFunction;
|
||||
// tilingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
|
||||
|
||||
painter->tilingPipeline = [surface->device newComputePipelineStateWithDescriptor: tilingPipelineDesc
|
||||
options: MTLPipelineOptionNone
|
||||
reflection: nil
|
||||
error: &error];
|
||||
|
||||
MTLComputePipelineDescriptor* sortingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init];
|
||||
sortingPipelineDesc.computeFunction = sortingFunction;
|
||||
// sortingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
|
||||
|
||||
painter->sortingPipeline = [surface->device newComputePipelineStateWithDescriptor: sortingPipelineDesc
|
||||
options: MTLPipelineOptionNone
|
||||
reflection: nil
|
||||
error: &error];
|
||||
|
||||
MTLComputePipelineDescriptor* boxingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init];
|
||||
boxingPipelineDesc.computeFunction = boxingFunction;
|
||||
// boxingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
|
||||
|
||||
painter->boxingPipeline = [surface->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 = surface->metalLayer.pixelFormat;
|
||||
|
||||
// create render pipeline
|
||||
painter->renderPipeline = [surface->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_painter_update_vertex_layout(painter);
|
||||
|
||||
return((mg_canvas_painter*)painter);
|
||||
}
|
||||
|
||||
|
||||
#undef LOG_SUBSYSTEM
|
|
@ -1,231 +0,0 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: graphics.mm
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 12/07/2020
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#import<Metal/Metal.h>
|
||||
#import<QuartzCore/CAMetalLayer.h>
|
||||
#include<simd/simd.h>
|
||||
|
||||
#include"graphics_internal.h"
|
||||
#include"macro_helpers.h"
|
||||
#include"osx_app.h"
|
||||
|
||||
#define LOG_SUBSYSTEM "Graphics"
|
||||
|
||||
static const u32 MP_METAL_MAX_DRAWABLES_IN_FLIGHT = 3;
|
||||
|
||||
typedef struct mg_metal_surface
|
||||
{
|
||||
mg_surface_info interface;
|
||||
|
||||
// permanent metal resources
|
||||
NSView* view;
|
||||
id<MTLDevice> device;
|
||||
CAMetalLayer* metalLayer;
|
||||
id<MTLCommandQueue> commandQueue;
|
||||
|
||||
// transient metal resources
|
||||
id<CAMetalDrawable> drawable;
|
||||
id<MTLCommandBuffer> commandBuffer;
|
||||
|
||||
dispatch_semaphore_t drawableSemaphore;
|
||||
|
||||
} mg_metal_surface;
|
||||
|
||||
void mg_metal_surface_acquire_drawable_and_command_buffer(mg_metal_surface* surface)
|
||||
{@autoreleasepool{
|
||||
/*WARN(martin): this is super important
|
||||
When the app is put in the background, it seems that if there are buffers in flight, the drawables to
|
||||
can be leaked. This causes the gpu to allocate more and more drawables, until the app crashes.
|
||||
(note: the drawable objects themselves are released once the app comes back to the forefront, but the
|
||||
memory allocated in the GPU is never freed...)
|
||||
|
||||
In background the gpu seems to create drawable if none is available instead of actually
|
||||
blocking on nextDrawable. These drawable never get freed.
|
||||
This is not a problem if our shader is fast enough, since a previous drawable becomes
|
||||
available before we finish the frame. But we want to protect against it anyway
|
||||
|
||||
The normal blocking mechanism of nextDrawable seems useless, so we implement our own scheme by
|
||||
counting the number of drawables available with a semaphore that gets decremented here and
|
||||
incremented in the presentedHandler of the drawable.
|
||||
Thus we ensure that we don't consume more drawables than we are able to draw.
|
||||
|
||||
//TODO: we _also_ should stop trying to render if we detect that the app is in the background
|
||||
or occluded, but we can't count only on this because there is a potential race between the
|
||||
notification of background mode and the rendering.
|
||||
|
||||
//TODO: We should set a reasonable timeout and skip the frame and log an error in case we are stalled
|
||||
for too long.
|
||||
*/
|
||||
dispatch_semaphore_wait(surface->drawableSemaphore, DISPATCH_TIME_FOREVER);
|
||||
|
||||
surface->drawable = [surface->metalLayer nextDrawable];
|
||||
ASSERT(surface->drawable != nil);
|
||||
|
||||
//TODO: make this a weak reference if we use ARC
|
||||
dispatch_semaphore_t semaphore = surface->drawableSemaphore;
|
||||
[surface->drawable addPresentedHandler:^(id<MTLDrawable> drawable){
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
|
||||
//NOTE(martin): create a command buffer
|
||||
surface->commandBuffer = [surface->commandQueue commandBuffer];
|
||||
|
||||
[surface->commandBuffer retain];
|
||||
[surface->drawable retain];
|
||||
}}
|
||||
|
||||
void mg_metal_surface_prepare(mg_surface_info* interface)
|
||||
{
|
||||
mg_metal_surface* surface = (mg_metal_surface*)interface;
|
||||
mg_metal_surface_acquire_drawable_and_command_buffer(surface);
|
||||
}
|
||||
|
||||
void mg_metal_surface_present(mg_surface_info* interface)
|
||||
{
|
||||
mg_metal_surface* surface = (mg_metal_surface*)interface;
|
||||
@autoreleasepool
|
||||
{
|
||||
//NOTE(martin): present drawable and commit command buffer
|
||||
[surface->commandBuffer presentDrawable: surface->drawable];
|
||||
[surface->commandBuffer commit];
|
||||
[surface->commandBuffer waitUntilCompleted];
|
||||
|
||||
//NOTE(martin): acquire next frame resources
|
||||
[surface->commandBuffer release];
|
||||
surface->commandBuffer = nil;
|
||||
[surface->drawable release];
|
||||
surface->drawable = nil;
|
||||
}
|
||||
}
|
||||
|
||||
void mg_metal_surface_destroy(mg_surface_info* interface)
|
||||
{
|
||||
mg_metal_surface* surface = (mg_metal_surface*)interface;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
[surface->commandQueue release];
|
||||
[surface->metalLayer release];
|
||||
[surface->device release];
|
||||
}
|
||||
}
|
||||
|
||||
void mg_metal_surface_resize(mg_surface_info* interface, u32 width, u32 height)
|
||||
{
|
||||
mg_metal_surface* surface = (mg_metal_surface*)interface;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
//TODO(martin): actually detect scaling
|
||||
CGRect frame = {{0, 0}, {width, height}};
|
||||
[surface->view setFrame: frame];
|
||||
f32 scale = surface->metalLayer.contentsScale;
|
||||
CGSize drawableSize = (CGSize){.width = width * scale, .height = height * scale};
|
||||
surface->metalLayer.drawableSize = drawableSize;
|
||||
}
|
||||
}
|
||||
|
||||
vec2 mg_metal_surface_get_size(mg_surface_info* interface)
|
||||
{
|
||||
mg_metal_surface* surface = (mg_metal_surface*)interface;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
//TODO(martin): actually detect scaling
|
||||
CGRect frame = surface->view.frame;
|
||||
return((vec2){frame.size.width, frame.size.height});
|
||||
}
|
||||
}
|
||||
|
||||
static const f32 MG_METAL_SURFACE_CONTENTS_SCALING = 2;
|
||||
|
||||
extern "C" mg_surface mg_metal_surface_create_for_view(mp_view view)
|
||||
{
|
||||
mp_view_data* viewData = mp_view_ptr_from_handle(view);
|
||||
if(!viewData)
|
||||
{
|
||||
return(mg_surface_nil());
|
||||
}
|
||||
else
|
||||
{
|
||||
mg_metal_surface* surface = (mg_metal_surface*)malloc(sizeof(mg_metal_surface));
|
||||
|
||||
//NOTE(martin): setup interface functions
|
||||
surface->interface.backend = MG_BACKEND_METAL;
|
||||
surface->interface.destroy = mg_metal_surface_destroy;
|
||||
surface->interface.prepare = mg_metal_surface_prepare;
|
||||
surface->interface.present = mg_metal_surface_present;
|
||||
surface->interface.resize = mg_metal_surface_resize;
|
||||
surface->interface.getSize = mg_metal_surface_get_size;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
surface->drawableSemaphore = dispatch_semaphore_create(MP_METAL_MAX_DRAWABLES_IN_FLIGHT);
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): create a metal device and a metal layer and
|
||||
//-----------------------------------------------------------
|
||||
surface->device = MTLCreateSystemDefaultDevice();
|
||||
[surface->device retain];
|
||||
surface->metalLayer = [CAMetalLayer layer];
|
||||
[surface->metalLayer retain];
|
||||
surface->metalLayer.device = surface->device;
|
||||
|
||||
surface->view = viewData->nsView;
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): set the size and scaling
|
||||
//-----------------------------------------------------------
|
||||
CGSize size = surface->view.bounds.size;
|
||||
size.width *= MG_METAL_SURFACE_CONTENTS_SCALING;
|
||||
size.height *= MG_METAL_SURFACE_CONTENTS_SCALING;
|
||||
surface->metalLayer.drawableSize = size;
|
||||
surface->metalLayer.contentsScale = MG_METAL_SURFACE_CONTENTS_SCALING;
|
||||
|
||||
surface->metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
|
||||
|
||||
surface->view.wantsLayer = YES;
|
||||
surface->view.layer = surface->metalLayer;
|
||||
|
||||
//NOTE(martin): handling resizing
|
||||
surface->view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
|
||||
surface->metalLayer.autoresizingMask = kCALayerHeightSizable | kCALayerWidthSizable;
|
||||
surface->metalLayer.needsDisplayOnBoundsChange = YES;
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): create a command queue
|
||||
//-----------------------------------------------------------
|
||||
surface->commandQueue = [surface->device newCommandQueue];
|
||||
[surface->commandQueue retain];
|
||||
|
||||
//NOTE(martin): command buffer and drawable are set on demand and at the end of each present() call
|
||||
surface->drawable = nil;
|
||||
surface->commandBuffer = nil;
|
||||
}
|
||||
|
||||
mg_surface handle = mg_surface_alloc_handle((mg_surface_info*)surface);
|
||||
viewData->surface = handle;
|
||||
|
||||
return(handle);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" mg_surface mg_metal_surface_create_for_window(mp_window window)
|
||||
{
|
||||
mp_window_data* windowData = mp_window_ptr_from_handle(window);
|
||||
if(!windowData)
|
||||
{
|
||||
return(mg_surface_nil());
|
||||
}
|
||||
else
|
||||
{
|
||||
return(mg_metal_surface_create_for_view(windowData->mainView));
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOG_SUBSYSTEM
|
|
@ -27,5 +27,6 @@
|
|||
#include"mp_app.h"
|
||||
#include"graphics.h"
|
||||
#include"ui.h"
|
||||
#include"hash.h"
|
||||
|
||||
#endif //__MILEPOST_H_
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: milepost.mm
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 13/02/2021
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
|
||||
#include"osx_app.mm"
|
||||
#include"metal_surface.mm"
|
||||
#include"metal_painter.mm"
|
|
@ -338,6 +338,7 @@ typedef void(*mp_event_callback)(mp_event event, void* data);
|
|||
void mp_set_event_callback(mp_event_callback callback, void* data);
|
||||
void mp_set_target_fps(u32 fps);
|
||||
void mp_run_loop();
|
||||
void mp_end_input_frame();
|
||||
|
||||
void mp_pump_events(f64 timeout);
|
||||
bool mp_next_event(mp_event* event);
|
||||
|
@ -345,8 +346,6 @@ bool mp_next_event(mp_event* event);
|
|||
typedef void(*mp_live_resize_callback)(mp_event event, void* data);
|
||||
void mp_set_live_resize_callback(mp_live_resize_callback callback, void* data);
|
||||
|
||||
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Input state polling
|
||||
//--------------------------------------------------------------------
|
||||
|
@ -373,7 +372,7 @@ str8 mp_input_text_utf8(mem_arena* arena);
|
|||
//--------------------------------------------------------------------
|
||||
// app resources
|
||||
//--------------------------------------------------------------------
|
||||
int mp_app_get_resource_path(const char* name, char** result);
|
||||
str8 mp_app_get_resource_path(mem_arena* arena, const char* name);
|
||||
str8 mp_app_get_executable_path(mem_arena* arena);
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
#include"mp_app.h"
|
||||
#include"graphics.h"
|
||||
|
||||
struct mp_window_data
|
||||
typedef struct mp_window_data
|
||||
{
|
||||
list_elt freeListElt;
|
||||
u32 generation;
|
||||
|
@ -31,9 +31,9 @@ struct mp_window_data
|
|||
bool hidden;
|
||||
|
||||
mp_view mainView;
|
||||
};
|
||||
} mp_window_data;
|
||||
|
||||
struct mp_view_data
|
||||
typedef struct mp_view_data
|
||||
{
|
||||
list_elt freeListElt;
|
||||
u32 generation;
|
||||
|
@ -41,7 +41,7 @@ struct mp_view_data
|
|||
mp_window window;
|
||||
NSView* nsView;
|
||||
mg_surface surface;
|
||||
};
|
||||
} mp_view_data;
|
||||
|
||||
@interface MPNativeWindow : NSWindow
|
||||
{
|
||||
|
|
2487
src/osx_app.mm
2487
src/osx_app.mm
File diff suppressed because it is too large
Load Diff
223
src/ui.c
223
src/ui.c
|
@ -101,7 +101,7 @@ typedef struct ui_context
|
|||
__thread ui_context __uiThreadContext = {0};
|
||||
__thread ui_context* __uiCurrentContext = 0;
|
||||
|
||||
ui_context* ui_get_context()
|
||||
ui_context* ui_get_context(void)
|
||||
{
|
||||
return(__uiCurrentContext);
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ mp_rect ui_intersect_rects(mp_rect lhs, mp_rect rhs)
|
|||
return(r);
|
||||
}
|
||||
|
||||
mp_rect ui_clip_top()
|
||||
mp_rect ui_clip_top(void)
|
||||
{
|
||||
mp_rect r = {-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX};
|
||||
ui_context* ui = ui_get_context();
|
||||
|
@ -166,13 +166,13 @@ void ui_clip_push(mp_rect clip)
|
|||
elt->clip = ui_intersect_rects(current, clip);
|
||||
}
|
||||
|
||||
void ui_clip_pop()
|
||||
void ui_clip_pop(void)
|
||||
{
|
||||
ui_context* ui = ui_get_context();
|
||||
ui_stack_pop(&ui->clipStack);
|
||||
}
|
||||
|
||||
ui_box* ui_box_top()
|
||||
ui_box* ui_box_top(void)
|
||||
{
|
||||
ui_context* ui = ui_get_context();
|
||||
ui_stack_elt* elt = ui->boxStack;
|
||||
|
@ -191,7 +191,7 @@ void ui_box_push(ui_box* box)
|
|||
}
|
||||
}
|
||||
|
||||
void ui_box_pop()
|
||||
void ui_box_pop(void)
|
||||
{
|
||||
ui_context* ui = ui_get_context();
|
||||
ui_box* box = ui_box_top();
|
||||
|
@ -243,7 +243,7 @@ void _cat2_(ui_push_, name)(type arg) \
|
|||
{ \
|
||||
_cat3_(ui_push_, name, _ext)(UI_STYLE_TAG_ANY, UI_STYLE_SEL_ANY, arg); \
|
||||
} \
|
||||
void _cat2_(ui_pop_, name)() \
|
||||
void _cat2_(ui_pop_, name)(void) \
|
||||
{ \
|
||||
ui_context* ui = ui_get_context(); \
|
||||
ui_stack_pop(&ui->stack); \
|
||||
|
@ -395,7 +395,7 @@ void ui_box_cache(ui_context* ui, ui_box* box)
|
|||
ListAppend(&(ui->boxMap[index]), &box->bucketElt);
|
||||
}
|
||||
|
||||
ui_box* ui_box_lookup(ui_context* ui, ui_key key)
|
||||
ui_box* ui_box_lookup_with_key(ui_context* ui, ui_key key)
|
||||
{
|
||||
u64 index = key.hash & (UI_BOX_MAP_BUCKET_COUNT-1);
|
||||
|
||||
|
@ -409,6 +409,18 @@ ui_box* ui_box_lookup(ui_context* ui, ui_key key)
|
|||
return(0);
|
||||
}
|
||||
|
||||
ui_box* ui_box_lookup_str8(str8 string)
|
||||
{
|
||||
ui_context* ui = ui_get_context();
|
||||
ui_key key = ui_key_from_string(string);
|
||||
return(ui_box_lookup_with_key(ui, key));
|
||||
}
|
||||
|
||||
ui_box* ui_box_lookup(const char* string)
|
||||
{
|
||||
return(ui_box_lookup_str8(str8_from_cstring((char*)string)));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// ui boxes
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -432,7 +444,7 @@ bool ui_box_hovering(ui_box* box, vec2 p)
|
|||
return(result);
|
||||
}
|
||||
|
||||
vec2 ui_mouse_position()
|
||||
vec2 ui_mouse_position(void)
|
||||
{
|
||||
ui_context* ui = ui_get_context();
|
||||
|
||||
|
@ -441,7 +453,7 @@ vec2 ui_mouse_position()
|
|||
return(mousePos);
|
||||
}
|
||||
|
||||
vec2 ui_mouse_delta()
|
||||
vec2 ui_mouse_delta(void)
|
||||
{
|
||||
ui_context* ui = ui_get_context();
|
||||
vec2 delta = mp_input_mouse_delta();
|
||||
|
@ -449,7 +461,7 @@ vec2 ui_mouse_delta()
|
|||
return(delta);
|
||||
}
|
||||
|
||||
vec2 ui_mouse_wheel()
|
||||
vec2 ui_mouse_wheel(void)
|
||||
{
|
||||
ui_context* ui = ui_get_context();
|
||||
vec2 delta = mp_input_mouse_wheel();
|
||||
|
@ -457,61 +469,12 @@ vec2 ui_mouse_wheel()
|
|||
return(delta);
|
||||
}
|
||||
|
||||
void ui_box_compute_signals(ui_context* ui, ui_box* box)
|
||||
{
|
||||
ui_sig* sig = mem_arena_alloc_type(&ui->frameArena, ui_sig);
|
||||
memset(sig, 0, sizeof(ui_sig));
|
||||
|
||||
if(!box->closed && !box->parentClosed)
|
||||
{
|
||||
vec2 mousePos = ui_mouse_position();
|
||||
|
||||
sig->hovering = ui_box_hovering(box, mousePos);
|
||||
|
||||
if(box->flags & UI_FLAG_CLICKABLE)
|
||||
{
|
||||
if(sig->hovering)
|
||||
{
|
||||
sig->pressed = mp_input_mouse_pressed(MP_MOUSE_LEFT);
|
||||
if(sig->pressed)
|
||||
{
|
||||
box->dragging = true;
|
||||
}
|
||||
|
||||
sig->clicked = mp_input_mouse_clicked(MP_MOUSE_LEFT);
|
||||
sig->doubleClicked = mp_input_mouse_clicked(MP_MOUSE_LEFT);
|
||||
}
|
||||
|
||||
sig->released = mp_input_mouse_released(MP_MOUSE_LEFT);
|
||||
if(sig->released)
|
||||
{
|
||||
if(box->dragging && sig->hovering)
|
||||
{
|
||||
sig->triggered = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!mp_input_mouse_down(MP_MOUSE_LEFT))
|
||||
{
|
||||
box->dragging = false;
|
||||
}
|
||||
|
||||
sig->dragging = box->dragging;
|
||||
}
|
||||
|
||||
sig->mouse = (vec2){mousePos.x - box->rect.x, mousePos.y - box->rect.y};
|
||||
sig->delta = ui_mouse_delta();
|
||||
sig->wheel = ui_mouse_wheel();
|
||||
}
|
||||
box->sig = sig;
|
||||
}
|
||||
|
||||
ui_box* ui_box_make_str8(str8 string, ui_flags flags)
|
||||
{
|
||||
ui_context* ui = ui_get_context();
|
||||
|
||||
ui_key key = ui_key_from_string(string);
|
||||
ui_box* box = ui_box_lookup(ui, key);
|
||||
ui_box* box = ui_box_lookup_with_key(ui, key);
|
||||
|
||||
if(!box)
|
||||
{
|
||||
|
@ -545,9 +508,6 @@ ui_box* ui_box_make_str8(str8 string, ui_flags flags)
|
|||
box->floating[UI_AXIS_Y] = false;
|
||||
box->layout = box->parent ? box->parent->layout : (ui_layout){0};
|
||||
|
||||
//NOTE: compute input signals
|
||||
ui_box_compute_signals(ui, box);
|
||||
|
||||
//NOTE: compute style
|
||||
ui_style_selector selector = UI_STYLE_SEL_NORMAL;
|
||||
if(box->hot)
|
||||
|
@ -583,7 +543,7 @@ ui_box* ui_box_begin(const char* cstring, ui_flags flags)
|
|||
return(ui_box_begin_str8(string, flags));
|
||||
}
|
||||
|
||||
ui_box* ui_box_end()
|
||||
ui_box* ui_box_end(void)
|
||||
{
|
||||
ui_context* ui = ui_get_context();
|
||||
ui_box* box = ui_box_top();
|
||||
|
@ -657,7 +617,53 @@ void ui_box_set_tag(ui_box* box, ui_style_tag tag)
|
|||
|
||||
ui_sig ui_box_sig(ui_box* box)
|
||||
{
|
||||
return(*box->sig);
|
||||
//NOTE: compute input signals
|
||||
ui_sig sig = {0};
|
||||
|
||||
sig.box = box;
|
||||
|
||||
if(!box->closed && !box->parentClosed)
|
||||
{
|
||||
vec2 mousePos = ui_mouse_position();
|
||||
|
||||
sig.hovering = ui_box_hovering(box, mousePos);
|
||||
|
||||
if(box->flags & UI_FLAG_CLICKABLE)
|
||||
{
|
||||
if(sig.hovering)
|
||||
{
|
||||
sig.pressed = mp_input_mouse_pressed(MP_MOUSE_LEFT);
|
||||
if(sig.pressed)
|
||||
{
|
||||
box->dragging = true;
|
||||
}
|
||||
|
||||
sig.clicked = mp_input_mouse_clicked(MP_MOUSE_LEFT);
|
||||
sig.doubleClicked = mp_input_mouse_double_clicked(MP_MOUSE_LEFT);
|
||||
}
|
||||
|
||||
sig.released = mp_input_mouse_released(MP_MOUSE_LEFT);
|
||||
if(sig.released)
|
||||
{
|
||||
if(box->dragging && sig.hovering)
|
||||
{
|
||||
sig.triggered = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!mp_input_mouse_down(MP_MOUSE_LEFT))
|
||||
{
|
||||
box->dragging = false;
|
||||
}
|
||||
|
||||
sig.dragging = box->dragging;
|
||||
}
|
||||
|
||||
sig.mouse = (vec2){mousePos.x - box->rect.x, mousePos.y - box->rect.y};
|
||||
sig.delta = ui_mouse_delta();
|
||||
sig.wheel = ui_mouse_wheel();
|
||||
}
|
||||
return(sig);
|
||||
}
|
||||
|
||||
bool ui_box_hidden(ui_box* box)
|
||||
|
@ -701,6 +707,13 @@ void ui_animate_color(ui_context* ui, mg_color* color, mg_color target, f32 anim
|
|||
}
|
||||
}
|
||||
|
||||
void ui_animate_ui_size(ui_context* ui, ui_size* size, ui_size target, f32 animationTime)
|
||||
{
|
||||
size->kind = target.kind;
|
||||
ui_animate_f32(ui, &size->value, target.value, animationTime);
|
||||
ui_animate_f32(ui, &size->strictness, target.strictness, animationTime);
|
||||
}
|
||||
|
||||
void ui_box_compute_styling(ui_context* ui, ui_box* box)
|
||||
{
|
||||
ui_style* targetStyle = box->targetStyle;
|
||||
|
@ -708,11 +721,8 @@ void ui_box_compute_styling(ui_context* ui, ui_box* box)
|
|||
|
||||
f32 animationTime = targetStyle->animationTime;
|
||||
|
||||
box->computedStyle.size[UI_AXIS_X] = targetStyle->size[UI_AXIS_X];
|
||||
box->computedStyle.size[UI_AXIS_Y] = targetStyle->size[UI_AXIS_Y];
|
||||
|
||||
//TODO: interpolate based on transition values
|
||||
u32 flags = box->computedStyle.animationFlags;
|
||||
//NOTE: interpolate based on transition values
|
||||
u32 flags = box->targetStyle->animationFlags;
|
||||
|
||||
if(box->fresh)
|
||||
{
|
||||
|
@ -720,6 +730,24 @@ void ui_box_compute_styling(ui_context* ui, ui_box* box)
|
|||
}
|
||||
else
|
||||
{
|
||||
if(flags & UI_STYLE_ANIMATE_SIZE_X)
|
||||
{
|
||||
ui_animate_ui_size(ui, &box->computedStyle.size[UI_AXIS_X], targetStyle->size[UI_AXIS_X], animationTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
box->computedStyle.size[UI_AXIS_X] = targetStyle->size[UI_AXIS_X];
|
||||
}
|
||||
|
||||
if(flags & UI_STYLE_ANIMATE_SIZE_Y)
|
||||
{
|
||||
ui_animate_ui_size(ui, &box->computedStyle.size[UI_AXIS_Y], targetStyle->size[UI_AXIS_Y], animationTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
box->computedStyle.size[UI_AXIS_Y] = targetStyle->size[UI_AXIS_Y];
|
||||
}
|
||||
|
||||
if(flags & UI_STYLE_ANIMATE_BG_COLOR)
|
||||
{
|
||||
ui_animate_color(ui, &box->computedStyle.bgColor, targetStyle->bgColor, animationTime);
|
||||
|
@ -920,6 +948,17 @@ void ui_layout_compute_rect(ui_context* ui, ui_box* box, vec2 pos)
|
|||
box->rect.y - box->scroll.y};
|
||||
vec2 currentPos = origin;
|
||||
|
||||
vec2 margin = {0, 0};
|
||||
for(int i=0; i<UI_AXIS_COUNT; i++)
|
||||
{
|
||||
if(box->computedStyle.size[i].kind == UI_SIZE_CHILDREN)
|
||||
{
|
||||
margin.c[i] = box->computedStyle.size[i].value;
|
||||
}
|
||||
}
|
||||
currentPos.x += margin.x;
|
||||
currentPos.y += margin.y;
|
||||
|
||||
vec2 contentsSize = {maximum(box->rect.w, box->childrenSum[UI_AXIS_X]),
|
||||
maximum(box->rect.h, box->childrenSum[UI_AXIS_Y])};
|
||||
|
||||
|
@ -927,7 +966,7 @@ void ui_layout_compute_rect(ui_context* ui, ui_box* box, vec2 pos)
|
|||
{
|
||||
if(align[i] == UI_ALIGN_END)
|
||||
{
|
||||
currentPos.c[i] += contentsSize.c[i] - box->childrenSum[i];
|
||||
currentPos.c[i] += contentsSize.c[i] - box->childrenSum[i] - margin.c[i];
|
||||
}
|
||||
}
|
||||
if(align[layoutAxis] == UI_ALIGN_CENTER)
|
||||
|
@ -1061,6 +1100,11 @@ void ui_draw_box(mg_canvas canvas, ui_box* box)
|
|||
ui_rectangle_fill(canvas, box->rect, style->roundness);
|
||||
}
|
||||
|
||||
if((box->flags & UI_FLAG_DRAW_RENDER_PROC) && box->renderProc)
|
||||
{
|
||||
box->renderProc(canvas, box, box->renderData);
|
||||
}
|
||||
|
||||
for_each_in_list(&box->children, child, ui_box, listElt)
|
||||
{
|
||||
ui_draw_box(canvas, child);
|
||||
|
@ -1088,11 +1132,6 @@ void ui_draw_box(mg_canvas canvas, ui_box* box)
|
|||
mg_fill(canvas);
|
||||
}
|
||||
|
||||
if((box->flags & UI_FLAG_DRAW_RENDER_PROC) && box->renderProc)
|
||||
{
|
||||
box->renderProc(canvas, box, box->renderData);
|
||||
}
|
||||
|
||||
if(box->flags & UI_FLAG_CLIP)
|
||||
{
|
||||
mg_clip_pop(canvas);
|
||||
|
@ -1196,7 +1235,7 @@ void ui_begin_frame(u32 width, u32 height, ui_style defaultStyle)
|
|||
ui_box_push(contents);
|
||||
}
|
||||
|
||||
void ui_end_frame()
|
||||
void ui_end_frame(void)
|
||||
{
|
||||
ui_context* ui = ui_get_context();
|
||||
|
||||
|
@ -1226,7 +1265,7 @@ void ui_end_frame()
|
|||
//-----------------------------------------------------------------------------
|
||||
// Init / cleanup
|
||||
//-----------------------------------------------------------------------------
|
||||
void ui_init()
|
||||
void ui_init(void)
|
||||
{
|
||||
ui_context* ui = &__uiThreadContext;
|
||||
if(!ui->init)
|
||||
|
@ -1240,7 +1279,7 @@ void ui_init()
|
|||
}
|
||||
}
|
||||
|
||||
void ui_cleanup()
|
||||
void ui_cleanup(void)
|
||||
{
|
||||
ui_context* ui = ui_get_context();
|
||||
mem_arena_release(&ui->frameArena);
|
||||
|
@ -1253,11 +1292,11 @@ void ui_cleanup()
|
|||
// Basic helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ui_sig ui_label(const char* label)
|
||||
ui_sig ui_label_str8(str8 label)
|
||||
{
|
||||
ui_flags flags = UI_FLAG_CLIP
|
||||
| UI_FLAG_DRAW_TEXT;
|
||||
ui_box* box = ui_box_make(label, flags);
|
||||
ui_box* box = ui_box_make_str8(label, flags);
|
||||
ui_box_set_size(box, UI_AXIS_X, UI_SIZE_TEXT, 0, 0);
|
||||
ui_box_set_size(box, UI_AXIS_Y, UI_SIZE_TEXT, 0, 0);
|
||||
|
||||
|
@ -1265,7 +1304,12 @@ ui_sig ui_label(const char* label)
|
|||
return(sig);
|
||||
}
|
||||
|
||||
ui_sig ui_button(const char* label)
|
||||
ui_sig ui_label(const char* label)
|
||||
{
|
||||
return(ui_label_str8(str8_from_cstring((char*)label)));
|
||||
}
|
||||
|
||||
ui_sig ui_button_str8(str8 label)
|
||||
{
|
||||
ui_flags flags = UI_FLAG_CLICKABLE
|
||||
| UI_FLAG_CLIP
|
||||
|
@ -1275,7 +1319,7 @@ ui_sig ui_button(const char* label)
|
|||
| UI_FLAG_HOT_ANIMATION
|
||||
| UI_FLAG_ACTIVE_ANIMATION;
|
||||
|
||||
ui_box* box = ui_box_make(label, flags);
|
||||
ui_box* box = ui_box_make_str8(label, flags);
|
||||
ui_box_set_tag(box, UI_STYLE_TAG_BUTTON);
|
||||
ui_sig sig = ui_box_sig(box);
|
||||
|
||||
|
@ -1299,6 +1343,11 @@ ui_sig ui_button(const char* label)
|
|||
return(sig);
|
||||
}
|
||||
|
||||
ui_sig ui_button(const char* label)
|
||||
{
|
||||
return(ui_button_str8(str8_from_cstring((char*)label)));
|
||||
}
|
||||
|
||||
ui_box* ui_scrollbar(const char* label, f32 thumbRatio, f32* scrollValue)
|
||||
{
|
||||
ui_box* frame = ui_box_begin(label, 0);
|
||||
|
@ -1317,6 +1366,7 @@ ui_box* ui_scrollbar(const char* label, f32 thumbRatio, f32* scrollValue)
|
|||
ui_push_fg_color_ext(UI_STYLE_TAG_ANY, UI_STYLE_SEL_HOT|UI_STYLE_SEL_ACTIVE, (mg_color){0, 0, 0, 0.7});
|
||||
ui_push_roundness_ext(UI_STYLE_TAG_ANY, UI_STYLE_SEL_HOT|UI_STYLE_SEL_ACTIVE, roundness);
|
||||
ui_push_animation_time_ext(UI_STYLE_TAG_ANY, UI_STYLE_SEL_ANY, 1.);
|
||||
ui_push_animation_flags_ext(UI_STYLE_TAG_ANY, UI_STYLE_SEL_ANY, UI_STYLE_ANIMATE_BG_COLOR|UI_STYLE_ANIMATE_FG_COLOR);
|
||||
|
||||
ui_flags trackFlags = UI_FLAG_CLIP
|
||||
| UI_FLAG_DRAW_BACKGROUND
|
||||
|
@ -1358,6 +1408,7 @@ ui_box* ui_scrollbar(const char* label, f32 thumbRatio, f32* scrollValue)
|
|||
ui_pop_fg_color();
|
||||
ui_pop_roundness();
|
||||
ui_pop_animation_time();
|
||||
ui_pop_animation_flags();
|
||||
|
||||
//NOTE: interaction
|
||||
ui_sig thumbSig = ui_box_sig(thumb);
|
||||
|
@ -1507,7 +1558,7 @@ ui_sig ui_tooltip_begin(const char* name)
|
|||
return(ui_box_sig(tooltip));
|
||||
}
|
||||
|
||||
void ui_tooltip_end()
|
||||
void ui_tooltip_end(void)
|
||||
{
|
||||
ui_box_pop(); // tooltip
|
||||
ui_box_pop(); // ui->overlay
|
||||
|
@ -1530,7 +1581,7 @@ void ui_menu_bar_begin(const char* name)
|
|||
}
|
||||
}
|
||||
|
||||
void ui_menu_bar_end()
|
||||
void ui_menu_bar_end(void)
|
||||
{
|
||||
ui_pop_size(UI_AXIS_X);
|
||||
ui_pop_size(UI_AXIS_Y);
|
||||
|
@ -1583,7 +1634,7 @@ void ui_menu_begin(const char* label)
|
|||
ui_box_push(menu);
|
||||
}
|
||||
|
||||
void ui_menu_end()
|
||||
void ui_menu_end(void)
|
||||
{
|
||||
ui_box_pop(); // menu
|
||||
ui_box_pop(); // overlay;
|
||||
|
|
76
src/ui.h
76
src/ui.h
|
@ -84,15 +84,16 @@ typedef enum { UI_STYLE_SEL_NORMAL = 1<<0,
|
|||
typedef u32 ui_style_tag;
|
||||
#define UI_STYLE_TAG_ANY (ui_style_tag)0
|
||||
|
||||
typedef enum { UI_STYLE_ANIMATE_SIZE = 1<<1,
|
||||
UI_STYLE_ANIMATE_BG_COLOR = 1<<2,
|
||||
UI_STYLE_ANIMATE_FG_COLOR = 1<<3,
|
||||
UI_STYLE_ANIMATE_BORDER_COLOR = 1<<4,
|
||||
UI_STYLE_ANIMATE_FONT_COLOR = 1<<5,
|
||||
UI_STYLE_ANIMATE_FONT_SIZE = 1<<6,
|
||||
UI_STYLE_ANIMATE_BORDER_SIZE = 1<<7,
|
||||
UI_STYLE_ANIMATE_ROUNDNESS = 1<<8,
|
||||
UI_STYLE_ANIMATE_POS = 1<<9,
|
||||
typedef enum { UI_STYLE_ANIMATE_SIZE_X = 1<<1,
|
||||
UI_STYLE_ANIMATE_SIZE_Y = 1<<2,
|
||||
UI_STYLE_ANIMATE_BG_COLOR = 1<<3,
|
||||
UI_STYLE_ANIMATE_FG_COLOR = 1<<4,
|
||||
UI_STYLE_ANIMATE_BORDER_COLOR = 1<<5,
|
||||
UI_STYLE_ANIMATE_FONT_COLOR = 1<<6,
|
||||
UI_STYLE_ANIMATE_FONT_SIZE = 1<<7,
|
||||
UI_STYLE_ANIMATE_BORDER_SIZE = 1<<8,
|
||||
UI_STYLE_ANIMATE_ROUNDNESS = 1<<9,
|
||||
UI_STYLE_ANIMATE_POS = 1<<10,
|
||||
} ui_style_animation_flags;
|
||||
|
||||
typedef struct ui_style
|
||||
|
@ -183,31 +184,41 @@ struct ui_box
|
|||
|
||||
typedef struct ui_context ui_context;
|
||||
|
||||
void ui_init();
|
||||
ui_context* ui_get_context();
|
||||
void ui_init(void);
|
||||
ui_context* ui_get_context(void);
|
||||
void ui_set_context(ui_context* context);
|
||||
|
||||
void ui_begin_frame(u32 width, u32 height, ui_style defaultStyle);
|
||||
void ui_end_frame();
|
||||
void ui_end_frame(void);
|
||||
void ui_draw(mg_canvas canvas);
|
||||
|
||||
ui_box* ui_box_lookup(const char* string);
|
||||
ui_box* ui_box_lookup_str8(str8 string);
|
||||
ui_box* ui_box_make(const char* string, ui_flags flags);
|
||||
ui_box* ui_box_begin(const char* string, ui_flags flags);
|
||||
ui_box* ui_box_make_str8(str8 string, ui_flags flags);
|
||||
ui_box* ui_box_begin_str8(str8 string, ui_flags flags);
|
||||
ui_box* ui_box_end();
|
||||
ui_box* ui_box_end(void);
|
||||
#define ui_container(name, flags) defer_loop(ui_box_begin(name, flags), ui_box_end())
|
||||
|
||||
void ui_box_push(ui_box* box);
|
||||
void ui_box_pop();
|
||||
ui_box* ui_box_top();
|
||||
void ui_box_pop(void);
|
||||
ui_box* ui_box_top(void);
|
||||
|
||||
bool ui_box_closed(ui_box* box);
|
||||
void ui_box_set_closed(ui_box* box, bool closed);
|
||||
|
||||
bool ui_box_active(ui_box* box);
|
||||
void ui_box_activate(ui_box* box);
|
||||
void ui_box_deactivate(ui_box* box);
|
||||
|
||||
bool ui_box_hot(ui_box* box);
|
||||
void ui_box_set_hot(ui_box* box, bool hot);
|
||||
|
||||
void ui_box_set_render_proc(ui_box* box, ui_box_render_proc proc, void* data);
|
||||
|
||||
void ui_box_set_layout(ui_box* box, ui_axis axis, ui_align alignX, ui_align alignY);
|
||||
void ui_box_set_size(ui_box* box, ui_axis axis, ui_size_kind kind, f32 value, f32 strictness);
|
||||
void ui_box_set_floating(ui_box* box, ui_axis axis, f32 pos);
|
||||
|
||||
void ui_box_set_style_selector(ui_box* box, ui_style_selector selector);
|
||||
|
||||
ui_sig ui_box_sig(ui_box* box);
|
||||
|
@ -238,16 +249,16 @@ void ui_push_roundness_ext(ui_style_tag tag, ui_style_selector selector, f32 rou
|
|||
void ui_push_animation_time_ext(ui_style_tag tag, ui_style_selector selector, f32 time);
|
||||
void ui_push_animation_flags_ext(ui_style_tag tag, ui_style_selector selector, u32 flags);
|
||||
|
||||
void ui_pop_bg_color();
|
||||
void ui_pop_fg_color();
|
||||
void ui_pop_font();
|
||||
void ui_pop_font_size();
|
||||
void ui_pop_font_color();
|
||||
void ui_pop_border_size();
|
||||
void ui_pop_border_color();
|
||||
void ui_pop_roundness();
|
||||
void ui_pop_animation_time();
|
||||
void ui_pop_animation_flags();
|
||||
void ui_pop_bg_color(void);
|
||||
void ui_pop_fg_color(void);
|
||||
void ui_pop_font(void);
|
||||
void ui_pop_font_size(void);
|
||||
void ui_pop_font_color(void);
|
||||
void ui_pop_border_size(void);
|
||||
void ui_pop_border_color(void);
|
||||
void ui_pop_roundness(void);
|
||||
void ui_pop_animation_time(void);
|
||||
void ui_pop_animation_flags(void);
|
||||
// Basic helpers
|
||||
|
||||
enum {
|
||||
|
@ -261,23 +272,26 @@ enum {
|
|||
};
|
||||
|
||||
ui_sig ui_label(const char* label);
|
||||
ui_sig ui_label_str8(str8 label);
|
||||
|
||||
ui_sig ui_button(const char* label);
|
||||
ui_sig ui_button_str8(str8 label);
|
||||
ui_box* ui_scrollbar(const char* label, f32 thumbRatio, f32* scrollValue);
|
||||
|
||||
ui_box* ui_panel_begin(const char* name);
|
||||
void ui_panel_end();
|
||||
void ui_panel_end(void);
|
||||
#define ui_panel(name) defer_loop(ui_panel_begin(name), ui_panel_end())
|
||||
|
||||
ui_sig ui_tooltip_begin(const char* name);
|
||||
void ui_tooltip_end();
|
||||
void ui_tooltip_end(void);
|
||||
#define ui_tooltip(name) defer_loop(ui_tooltip_begin(name), ui_tooltip_end())
|
||||
|
||||
void ui_menu_bar_begin(const char* label);
|
||||
void ui_menu_bar_end();
|
||||
void ui_menu_bar_end(void);
|
||||
#define ui_menu_bar(name) defer_loop(ui_menu_bar_begin(name), ui_menu_bar_end())
|
||||
|
||||
void ui_menu_begin(const char* label);
|
||||
void ui_menu_end();
|
||||
void ui_menu_end(void);
|
||||
#define ui_menu(name) defer_loop(ui_menu_begin(name), ui_menu_end())
|
||||
|
||||
typedef struct ui_text_box_result
|
||||
|
|
|
@ -23,9 +23,16 @@ str8 str8_from_buffer(u64 len, char* buffer)
|
|||
}
|
||||
|
||||
str8 str8_from_cstring(char* str)
|
||||
{
|
||||
if(!str)
|
||||
{
|
||||
return((str8){0});
|
||||
}
|
||||
else
|
||||
{
|
||||
return(str8_from_buffer(strlen(str), (char*)str));
|
||||
}
|
||||
}
|
||||
|
||||
str8 str8_slice(str8 s, u64 start, u64 end)
|
||||
{
|
||||
|
@ -43,9 +50,16 @@ str8 str8_push_buffer(mem_arena* arena, u64 len, char* buffer)
|
|||
}
|
||||
|
||||
str8 str8_push_cstring(mem_arena* arena, const char* str)
|
||||
{
|
||||
if(!str)
|
||||
{
|
||||
return((str8){0});
|
||||
}
|
||||
else
|
||||
{
|
||||
return(str8_push_buffer(arena, strlen(str), (char*)str));
|
||||
}
|
||||
}
|
||||
|
||||
str8 str8_push_copy(mem_arena* arena, str8 s)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue