trying to integrate mg_surface_client/server

This commit is contained in:
Martin Fouilleul 2022-08-19 19:46:31 +02:00
parent 0becc301d0
commit 13cccdf5de
14 changed files with 601 additions and 3330 deletions

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -27,5 +27,6 @@
#include"mp_app.h"
#include"graphics.h"
#include"ui.h"
#include"hash.h"
#endif //__MILEPOST_H_

View File

@ -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"

View File

@ -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);
//--------------------------------------------------------------------

View File

@ -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
{

File diff suppressed because it is too large Load Diff

223
src/ui.c
View File

@ -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;

View File

@ -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

View File

@ -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)
{