diff --git a/examples/win_canvas/main.c b/examples/win_canvas/main.c index 8b1dfee..c84449e 100644 --- a/examples/win_canvas/main.c +++ b/examples/win_canvas/main.c @@ -58,8 +58,10 @@ int main() mp_init(); mp_clock_init(); //TODO put that in mp_init()? - mp_rect rect = {.x = 100, .y = 100, .w = 810, .h = 610}; - mp_window window = mp_window_create(rect, "test", 0); + mp_rect windowRect = {.x = 100, .y = 100, .w = 810, .h = 610}; + mp_window window = mp_window_create(windowRect, "test", 0); + + mp_rect contentRect = mp_window_get_content_rect(window); //NOTE: create surface #if defined(OS_MACOS) @@ -70,6 +72,8 @@ int main() #error "unsupported OS" #endif + mg_surface_swap_interval(surface, 0); + //TODO: create canvas mg_canvas canvas = mg_canvas_create(surface); mg_font font = create_font(); @@ -79,7 +83,8 @@ int main() mp_window_focus(window); f32 x = 400, y = 300; - f32 dx = 5, dy = 5; + f32 speed = 0; + f32 dx = speed, dy = speed; f64 frameTime = 0; while(!mp_should_quit()) @@ -110,24 +115,36 @@ int main() { if(event.key.action == MP_KEY_PRESS || event.key.action == MP_KEY_REPEAT) { - /* + //* if(event.key.code == MP_KEY_LEFT) { - dx-=5.1; + if(x - 200 > 0) + { + x-=1; + } } else if(event.key.code == MP_KEY_RIGHT) { - dx+=5.1; + if(x + 200 < contentRect.w) + { + x+=1; + } } else if(event.key.code == MP_KEY_UP) { - dy+=5.1; + if(y + 200 < contentRect.h) + { + y+=1; + } } else if(event.key.code == MP_KEY_DOWN) { - dy-=5.1; + if(y - 200 > 0) + { + y-=1; + } } - */ + //*/ } } break; @@ -138,19 +155,19 @@ int main() if(x-200 < 0) { - dx = 5; + dx = speed; } - if(x+200 > 800) + if(x+200 > contentRect.w) { - dx = -5; + dx = -speed; } if(y-200 < 0) { - dy = 5; + dy = speed; } - if(y+200 > 550) + if(y+200 > contentRect.h) { - dy = -5; + dy = -speed; } x += dx; y += dy; diff --git a/src/graphics.c b/src/graphics.c index 4d89e58..26aa1a6 100644 --- a/src/graphics.c +++ b/src/graphics.c @@ -321,6 +321,16 @@ void mg_surface_present(mg_surface surface) } } +void mg_surface_swap_interval(mg_surface surface, int swap) +{ + DEBUG_ASSERT(__mgData.init); + mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); + if(surfaceData) + { + surfaceData->swapInterval(surfaceData, swap); + } +} + void mg_surface_set_frame(mg_surface surface, mp_rect frame) { DEBUG_ASSERT(__mgData.init); diff --git a/src/graphics.h b/src/graphics.h index 194eb1e..825c892 100644 --- a/src/graphics.h +++ b/src/graphics.h @@ -1,210 +1,211 @@ -/************************************************************//** -* -* @file: graphics.h -* @author: Martin Fouilleul -* @date: 23/01/2023 -* @revision: -* -*****************************************************************/ -#ifndef __GRAPHICS_H_ -#define __GRAPHICS_H_ - -//------------------------------------------------------------------------------------------ -//NOTE(martin): graphics surface -//------------------------------------------------------------------------------------------ -typedef struct mg_surface { u64 h; } mg_surface; - -mg_surface mg_surface_nil(); -bool mg_surface_is_nil(mg_surface surface); - -void mg_surface_destroy(mg_surface surface); -void mg_surface_prepare(mg_surface surface); -void mg_surface_present(mg_surface surface); -mp_rect mg_surface_get_frame(mg_surface surface); -void mg_surface_set_frame(mg_surface surface, mp_rect frame); -bool mg_surface_get_hidden(mg_surface surface); -void mg_surface_set_hidden(mg_surface surface, bool hidden); - - -//------------------------------------------------------------------------------------------ -//NOTE(martin): graphics canvas structs -//------------------------------------------------------------------------------------------ -typedef struct mg_canvas { u64 h; } mg_canvas; -typedef struct mg_stream { u64 h; } mg_stream; -typedef struct mg_font { u64 h; } mg_font; - -typedef struct mg_mat2x3 -{ - f32 m[6]; -} mg_mat2x3; - -typedef struct mg_color -{ - union - { - struct - { - f32 r; - f32 g; - f32 b; - f32 a; - }; - f32 c[4]; - }; -} mg_color; - -typedef enum {MG_JOINT_MITER = 0, - MG_JOINT_BEVEL, - MG_JOINT_NONE } mg_joint_type; - -typedef enum {MG_CAP_NONE = 0, - MG_CAP_SQUARE } mg_cap_type; - -typedef struct mg_font_extents -{ - f32 ascent; // the extent above the baseline (by convention a positive value extends above the baseline) - f32 descent; // the extent below the baseline (by convention, positive value extends below the baseline) - f32 leading; // spacing between one row's descent and the next row's ascent - f32 xHeight; // height of the lower case letter 'x' - f32 capHeight; // height of the upper case letter 'M' - f32 width; // maximum width of the font - -} mg_font_extents; - -typedef struct mg_text_extents -{ - f32 xBearing; - f32 yBearing; - f32 width; - f32 height; - f32 xAdvance; - f32 yAdvance; - -} mg_text_extents; - -//------------------------------------------------------------------------------------------ -//NOTE(martin): graphics canvas -//------------------------------------------------------------------------------------------ - -mg_canvas mg_canvas_create(mg_surface surface); -void mg_canvas_destroy(mg_canvas canvas); -mg_canvas mg_canvas_set_current(mg_canvas canvas); - -void mg_flush(); - -//------------------------------------------------------------------------------------------ -//NOTE(martin): transform, viewport and clipping -//------------------------------------------------------------------------------------------ -void mg_viewport(mp_rect viewPort); - -void mg_matrix_push(mg_mat2x3 matrix); -void mg_matrix_pop(); - -void mg_clip_push(f32 x, f32 y, f32 w, f32 h); -void mg_clip_pop(); - -//------------------------------------------------------------------------------------------ -//NOTE(martin): graphics attributes setting/getting -//------------------------------------------------------------------------------------------ -void mg_set_color(mg_color color); -void mg_set_color_rgba(f32 r, f32 g, f32 b, f32 a); -void mg_set_width(f32 width); -void mg_set_tolerance(f32 tolerance); -void mg_set_joint(mg_joint_type joint); -void mg_set_max_joint_excursion(f32 maxJointExcursion); -void mg_set_cap(mg_cap_type cap); -void mg_set_font(mg_font font); -void mg_set_font_size(f32 size); -void mg_set_text_flip(bool flip); - -mg_color mg_get_color(); -f32 mg_get_width(); -f32 mg_get_tolerance(); -mg_joint_type mg_get_joint(); -f32 mg_get_max_joint_excursion(); -mg_cap_type mg_get_cap(); -mg_font mg_get_font(); -f32 mg_get_font_size(); -bool mg_get_text_flip(); - -//------------------------------------------------------------------------------------------ -//NOTE(martin): path construction -//------------------------------------------------------------------------------------------ -vec2 mg_get_position(); -void mg_move_to(f32 x, f32 y); -void mg_line_to(f32 x, f32 y); -void mg_quadratic_to(f32 x1, f32 y1, f32 x2, f32 y2); -void mg_cubic_to(f32 x1, f32 y1, f32 x2, f32 y2, f32 x3, f32 y3); -void mg_close_path(); - -mp_rect mg_glyph_outlines(str32 glyphIndices); -void mg_codepoints_outlines(str32 string); -void mg_text_outlines(str8 string); - -//------------------------------------------------------------------------------------------ -//NOTE(martin): clear/fill/stroke -//------------------------------------------------------------------------------------------ -void mg_clear(); -void mg_fill(); -void mg_stroke(); - -//------------------------------------------------------------------------------------------ -//NOTE(martin): 'fast' shapes primitives -//------------------------------------------------------------------------------------------ -void mg_rectangle_fill(f32 x, f32 y, f32 w, f32 h); -void mg_rectangle_stroke(f32 x, f32 y, f32 w, f32 h); -void mg_rounded_rectangle_fill(f32 x, f32 y, f32 w, f32 h, f32 r); -void mg_rounded_rectangle_stroke(f32 x, f32 y, f32 w, f32 h, f32 r); -void mg_ellipse_fill(f32 x, f32 y, f32 rx, f32 ry); -void mg_ellipse_stroke(f32 x, f32 y, f32 rx, f32 ry); -void mg_circle_fill(f32 x, f32 y, f32 r); -void mg_circle_stroke(f32 x, f32 y, f32 r); -void mg_arc(f32 x, f32 y, f32 r, f32 arcAngle, f32 startAngle); - -//------------------------------------------------------------------------------------------ -//NOTE(martin): fonts -//------------------------------------------------------------------------------------------ -mg_font mg_font_nil(); -mg_font mg_font_create_from_memory(u32 size, byte* buffer, u32 rangeCount, unicode_range* ranges); -void mg_font_destroy(mg_font font); - -//NOTE(martin): the following int valued functions return -1 if font is invalid or codepoint is not present in font// -//TODO(martin): add enum error codes - -mg_font_extents mg_font_get_extents(mg_font font); -mg_font_extents mg_font_get_scaled_extents(mg_font font, f32 emSize); -f32 mg_font_get_scale_for_em_pixels(mg_font font, f32 emSize); - -//NOTE(martin): if you need to process more than one codepoint, first convert your codepoints to glyph indices, then use the -// glyph index versions of the functions, which can take an array of glyph indices. - -str32 mg_font_get_glyph_indices(mg_font font, str32 codePoints, str32 backing); -str32 mg_font_push_glyph_indices(mg_font font, mem_arena* arena, str32 codePoints); -u32 mg_font_get_glyph_index(mg_font font, utf32 codePoint); - -int mg_font_get_codepoint_extents(mg_font font, utf32 codePoint, mg_text_extents* outExtents); - -int mg_font_get_glyph_extents(mg_font font, str32 glyphIndices, mg_text_extents* outExtents); - -mp_rect mg_text_bounding_box_utf32(mg_font font, f32 fontSize, str32 text); -mp_rect mg_text_bounding_box(mg_font font, f32 fontSize, str8 text); - -//------------------------------------------------------------------------------------------ -//NOTE(martin): images -//------------------------------------------------------------------------------------------ -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(u32 width, u32 height, u8* pixels); -mg_image mg_image_create_from_data(str8 data, bool flip); -mg_image mg_image_create_from_file(str8 path, bool flip); - -void mg_image_drestroy(mg_image image); - -vec2 mg_image_size(mg_image image); -void mg_image_draw(mg_image image, mp_rect rect); -void mg_rounded_image_draw(mg_image image, mp_rect rect, f32 roundness); - -#endif //__GRAPHICS_H_ +/************************************************************//** +* +* @file: graphics.h +* @author: Martin Fouilleul +* @date: 23/01/2023 +* @revision: +* +*****************************************************************/ +#ifndef __GRAPHICS_H_ +#define __GRAPHICS_H_ + +//------------------------------------------------------------------------------------------ +//NOTE(martin): graphics surface +//------------------------------------------------------------------------------------------ +typedef struct mg_surface { u64 h; } mg_surface; + +mg_surface mg_surface_nil(); +bool mg_surface_is_nil(mg_surface surface); + +void mg_surface_destroy(mg_surface surface); +void mg_surface_prepare(mg_surface surface); +void mg_surface_present(mg_surface surface); +void mg_surface_swap_interval(mg_surface surface, int swap); +mp_rect mg_surface_get_frame(mg_surface surface); +void mg_surface_set_frame(mg_surface surface, mp_rect frame); +bool mg_surface_get_hidden(mg_surface surface); +void mg_surface_set_hidden(mg_surface surface, bool hidden); + + +//------------------------------------------------------------------------------------------ +//NOTE(martin): graphics canvas structs +//------------------------------------------------------------------------------------------ +typedef struct mg_canvas { u64 h; } mg_canvas; +typedef struct mg_stream { u64 h; } mg_stream; +typedef struct mg_font { u64 h; } mg_font; + +typedef struct mg_mat2x3 +{ + f32 m[6]; +} mg_mat2x3; + +typedef struct mg_color +{ + union + { + struct + { + f32 r; + f32 g; + f32 b; + f32 a; + }; + f32 c[4]; + }; +} mg_color; + +typedef enum {MG_JOINT_MITER = 0, + MG_JOINT_BEVEL, + MG_JOINT_NONE } mg_joint_type; + +typedef enum {MG_CAP_NONE = 0, + MG_CAP_SQUARE } mg_cap_type; + +typedef struct mg_font_extents +{ + f32 ascent; // the extent above the baseline (by convention a positive value extends above the baseline) + f32 descent; // the extent below the baseline (by convention, positive value extends below the baseline) + f32 leading; // spacing between one row's descent and the next row's ascent + f32 xHeight; // height of the lower case letter 'x' + f32 capHeight; // height of the upper case letter 'M' + f32 width; // maximum width of the font + +} mg_font_extents; + +typedef struct mg_text_extents +{ + f32 xBearing; + f32 yBearing; + f32 width; + f32 height; + f32 xAdvance; + f32 yAdvance; + +} mg_text_extents; + +//------------------------------------------------------------------------------------------ +//NOTE(martin): graphics canvas +//------------------------------------------------------------------------------------------ + +mg_canvas mg_canvas_create(mg_surface surface); +void mg_canvas_destroy(mg_canvas canvas); +mg_canvas mg_canvas_set_current(mg_canvas canvas); + +void mg_flush(); + +//------------------------------------------------------------------------------------------ +//NOTE(martin): transform, viewport and clipping +//------------------------------------------------------------------------------------------ +void mg_viewport(mp_rect viewPort); + +void mg_matrix_push(mg_mat2x3 matrix); +void mg_matrix_pop(); + +void mg_clip_push(f32 x, f32 y, f32 w, f32 h); +void mg_clip_pop(); + +//------------------------------------------------------------------------------------------ +//NOTE(martin): graphics attributes setting/getting +//------------------------------------------------------------------------------------------ +void mg_set_color(mg_color color); +void mg_set_color_rgba(f32 r, f32 g, f32 b, f32 a); +void mg_set_width(f32 width); +void mg_set_tolerance(f32 tolerance); +void mg_set_joint(mg_joint_type joint); +void mg_set_max_joint_excursion(f32 maxJointExcursion); +void mg_set_cap(mg_cap_type cap); +void mg_set_font(mg_font font); +void mg_set_font_size(f32 size); +void mg_set_text_flip(bool flip); + +mg_color mg_get_color(); +f32 mg_get_width(); +f32 mg_get_tolerance(); +mg_joint_type mg_get_joint(); +f32 mg_get_max_joint_excursion(); +mg_cap_type mg_get_cap(); +mg_font mg_get_font(); +f32 mg_get_font_size(); +bool mg_get_text_flip(); + +//------------------------------------------------------------------------------------------ +//NOTE(martin): path construction +//------------------------------------------------------------------------------------------ +vec2 mg_get_position(); +void mg_move_to(f32 x, f32 y); +void mg_line_to(f32 x, f32 y); +void mg_quadratic_to(f32 x1, f32 y1, f32 x2, f32 y2); +void mg_cubic_to(f32 x1, f32 y1, f32 x2, f32 y2, f32 x3, f32 y3); +void mg_close_path(); + +mp_rect mg_glyph_outlines(str32 glyphIndices); +void mg_codepoints_outlines(str32 string); +void mg_text_outlines(str8 string); + +//------------------------------------------------------------------------------------------ +//NOTE(martin): clear/fill/stroke +//------------------------------------------------------------------------------------------ +void mg_clear(); +void mg_fill(); +void mg_stroke(); + +//------------------------------------------------------------------------------------------ +//NOTE(martin): 'fast' shapes primitives +//------------------------------------------------------------------------------------------ +void mg_rectangle_fill(f32 x, f32 y, f32 w, f32 h); +void mg_rectangle_stroke(f32 x, f32 y, f32 w, f32 h); +void mg_rounded_rectangle_fill(f32 x, f32 y, f32 w, f32 h, f32 r); +void mg_rounded_rectangle_stroke(f32 x, f32 y, f32 w, f32 h, f32 r); +void mg_ellipse_fill(f32 x, f32 y, f32 rx, f32 ry); +void mg_ellipse_stroke(f32 x, f32 y, f32 rx, f32 ry); +void mg_circle_fill(f32 x, f32 y, f32 r); +void mg_circle_stroke(f32 x, f32 y, f32 r); +void mg_arc(f32 x, f32 y, f32 r, f32 arcAngle, f32 startAngle); + +//------------------------------------------------------------------------------------------ +//NOTE(martin): fonts +//------------------------------------------------------------------------------------------ +mg_font mg_font_nil(); +mg_font mg_font_create_from_memory(u32 size, byte* buffer, u32 rangeCount, unicode_range* ranges); +void mg_font_destroy(mg_font font); + +//NOTE(martin): the following int valued functions return -1 if font is invalid or codepoint is not present in font// +//TODO(martin): add enum error codes + +mg_font_extents mg_font_get_extents(mg_font font); +mg_font_extents mg_font_get_scaled_extents(mg_font font, f32 emSize); +f32 mg_font_get_scale_for_em_pixels(mg_font font, f32 emSize); + +//NOTE(martin): if you need to process more than one codepoint, first convert your codepoints to glyph indices, then use the +// glyph index versions of the functions, which can take an array of glyph indices. + +str32 mg_font_get_glyph_indices(mg_font font, str32 codePoints, str32 backing); +str32 mg_font_push_glyph_indices(mg_font font, mem_arena* arena, str32 codePoints); +u32 mg_font_get_glyph_index(mg_font font, utf32 codePoint); + +int mg_font_get_codepoint_extents(mg_font font, utf32 codePoint, mg_text_extents* outExtents); + +int mg_font_get_glyph_extents(mg_font font, str32 glyphIndices, mg_text_extents* outExtents); + +mp_rect mg_text_bounding_box_utf32(mg_font font, f32 fontSize, str32 text); +mp_rect mg_text_bounding_box(mg_font font, f32 fontSize, str8 text); + +//------------------------------------------------------------------------------------------ +//NOTE(martin): images +//------------------------------------------------------------------------------------------ +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(u32 width, u32 height, u8* pixels); +mg_image mg_image_create_from_data(str8 data, bool flip); +mg_image mg_image_create_from_file(str8 path, bool flip); + +void mg_image_drestroy(mg_image image); + +vec2 mg_image_size(mg_image image); +void mg_image_draw(mg_image image, mp_rect rect); +void mg_rounded_image_draw(mg_image image, mp_rect rect, f32 roundness); + +#endif //__GRAPHICS_H_ diff --git a/src/graphics_internal.h b/src/graphics_internal.h index 7392a02..e764021 100644 --- a/src/graphics_internal.h +++ b/src/graphics_internal.h @@ -27,6 +27,7 @@ typedef struct mg_surface_data mg_surface_data; typedef void (*mg_surface_destroy_proc)(mg_surface_data* surface); typedef void (*mg_surface_prepare_proc)(mg_surface_data* surface); typedef void (*mg_surface_present_proc)(mg_surface_data* surface); +typedef void (*mg_surface_swap_interval_proc)(mg_surface_data* surface, int swap); typedef mp_rect (*mg_surface_get_frame_proc)(mg_surface_data* surface); typedef void (*mg_surface_set_frame_proc)(mg_surface_data* surface, mp_rect frame); typedef bool (*mg_surface_get_hidden_proc)(mg_surface_data* surface); @@ -39,6 +40,7 @@ typedef struct mg_surface_data mg_surface_destroy_proc destroy; mg_surface_prepare_proc prepare; mg_surface_present_proc present; + mg_surface_swap_interval_proc swapInterval; mg_surface_get_frame_proc getFrame; mg_surface_set_frame_proc setFrame; mg_surface_get_hidden_proc getHidden; diff --git a/src/metal_surface.m b/src/metal_surface.m index 4157b24..91716a2 100644 --- a/src/metal_surface.m +++ b/src/metal_surface.m @@ -1,299 +1,309 @@ -/************************************************************//** -* -* @file: graphics.m -* @author: Martin Fouilleul -* @date: 12/07/2020 -* @revision: -* -*****************************************************************/ -#import -#import -#import -#include - -#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_data interface; - - // permanent metal resources - NSView* view; - id device; - CAMetalLayer* metalLayer; - id commandQueue; - - // transient metal resources - id drawable; - id commandBuffer; - - dispatch_semaphore_t drawableSemaphore; - -} mg_metal_surface; - -void mg_metal_surface_destroy(mg_surface_data* interface) -{ - mg_metal_surface* surface = (mg_metal_surface*)interface; - - @autoreleasepool - { - [surface->commandQueue release]; - [surface->metalLayer release]; - [surface->device release]; - } -} - -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 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_data* interface) -{ - mg_metal_surface* surface = (mg_metal_surface*)interface; - mg_metal_surface_acquire_drawable_and_command_buffer(surface); - - @autoreleasepool - { - MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; - renderPassDescriptor.colorAttachments[0].texture = surface->drawable.texture; - renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; - renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0,0.0,0.0,0.0); - - id renderEncoder = [surface->commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; - [renderEncoder endEncoding]; - } -} - -void mg_metal_surface_present(mg_surface_data* 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_set_frame(mg_surface_data* interface, mp_rect frame) -{ - mg_metal_surface* surface = (mg_metal_surface*)interface; - - @autoreleasepool - { - CGRect cgFrame = {{frame.x, frame.y}, {frame.w, frame.h}}; - [surface->view setFrame: cgFrame]; - f32 scale = surface->metalLayer.contentsScale; - CGSize drawableSize = (CGSize){.width = frame.w * scale, .height = frame.h * scale}; - surface->metalLayer.drawableSize = drawableSize; - } -} - -mp_rect mg_metal_surface_get_frame(mg_surface_data* interface) -{ - mg_metal_surface* surface = (mg_metal_surface*)interface; - - @autoreleasepool - { - CGRect frame = surface->view.frame; - return((mp_rect){frame.origin.x, frame.origin.y, frame.size.width, frame.size.height}); - } -} - -void mg_metal_surface_set_hidden(mg_surface_data* interface, bool hidden) -{ - mg_metal_surface* surface = (mg_metal_surface*)interface; - @autoreleasepool - { - [CATransaction begin]; - [CATransaction setDisableActions:YES]; - [surface->metalLayer setHidden:hidden]; - [CATransaction commit]; - } -} - -bool mg_metal_surface_get_hidden(mg_surface_data* interface) -{ - mg_metal_surface* surface = (mg_metal_surface*)interface; - @autoreleasepool - { - return([surface->metalLayer isHidden]); - } -} - -//TODO fix that according to real scaling, depending on the monitor settings -static const f32 MG_METAL_SURFACE_CONTENTS_SCALING = 2; - -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 - { - 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.getFrame = mg_metal_surface_get_frame; - surface->interface.setFrame = mg_metal_surface_set_frame; - surface->interface.getHidden = mg_metal_surface_get_hidden; - surface->interface.setHidden = mg_metal_surface_set_hidden; - - @autoreleasepool - { - NSRect frame = [[windowData->osx.nsWindow contentView] frame]; - surface->view = [[NSView alloc] initWithFrame: frame]; - [surface->view setWantsLayer:YES]; - - [[windowData->osx.nsWindow contentView] addSubview: surface->view]; - - 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 setOpaque:NO]; - - surface->metalLayer.device = surface->device; - - //----------------------------------------------------------- - //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_data*)surface); - return(handle); - } -} - -void* mg_metal_surface_layer(mg_surface surface) -{ - mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); - if(surfaceData && surfaceData->backend == MG_BACKEND_METAL) - { - mg_metal_surface* metalSurface = (mg_metal_surface*)surfaceData; - return(metalSurface->metalLayer); - } - else - { - return(nil); - } -} - -void* mg_metal_surface_drawable(mg_surface surface) -{ - mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); - if(surfaceData && surfaceData->backend == MG_BACKEND_METAL) - { - mg_metal_surface* metalSurface = (mg_metal_surface*)surfaceData; - return(metalSurface->drawable); - } - else - { - return(nil); - } -} - -void* mg_metal_surface_command_buffer(mg_surface surface) -{ - mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); - if(surfaceData && surfaceData->backend == MG_BACKEND_METAL) - { - mg_metal_surface* metalSurface = (mg_metal_surface*)surfaceData; - return(metalSurface->commandBuffer); - } - else - { - return(nil); - } -} - - -#undef LOG_SUBSYSTEM +/************************************************************//** +* +* @file: graphics.m +* @author: Martin Fouilleul +* @date: 12/07/2020 +* @revision: +* +*****************************************************************/ +#import +#import +#import +#include + +#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_data interface; + + // permanent metal resources + NSView* view; + id device; + CAMetalLayer* metalLayer; + id commandQueue; + + // transient metal resources + id drawable; + id commandBuffer; + + dispatch_semaphore_t drawableSemaphore; + +} mg_metal_surface; + +void mg_metal_surface_destroy(mg_surface_data* interface) +{ + mg_metal_surface* surface = (mg_metal_surface*)interface; + + @autoreleasepool + { + [surface->commandQueue release]; + [surface->metalLayer release]; + [surface->device release]; + } +} + +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 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_data* interface) +{ + mg_metal_surface* surface = (mg_metal_surface*)interface; + mg_metal_surface_acquire_drawable_and_command_buffer(surface); + + @autoreleasepool + { + MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; + renderPassDescriptor.colorAttachments[0].texture = surface->drawable.texture; + renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0,0.0,0.0,0.0); + + id renderEncoder = [surface->commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + [renderEncoder endEncoding]; + } +} + +void mg_metal_surface_present(mg_surface_data* 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_swap_interval(mg_surface_data* interface, int swap) +{ + mg_metal_surface* surface = (mg_metal_surface*)interface; + + //////////////////////////////////////////////////////////////// + //TODO + //////////////////////////////////////////////////////////////// +} + +void mg_metal_surface_set_frame(mg_surface_data* interface, mp_rect frame) +{ + mg_metal_surface* surface = (mg_metal_surface*)interface; + + @autoreleasepool + { + CGRect cgFrame = {{frame.x, frame.y}, {frame.w, frame.h}}; + [surface->view setFrame: cgFrame]; + f32 scale = surface->metalLayer.contentsScale; + CGSize drawableSize = (CGSize){.width = frame.w * scale, .height = frame.h * scale}; + surface->metalLayer.drawableSize = drawableSize; + } +} + +mp_rect mg_metal_surface_get_frame(mg_surface_data* interface) +{ + mg_metal_surface* surface = (mg_metal_surface*)interface; + + @autoreleasepool + { + CGRect frame = surface->view.frame; + return((mp_rect){frame.origin.x, frame.origin.y, frame.size.width, frame.size.height}); + } +} + +void mg_metal_surface_set_hidden(mg_surface_data* interface, bool hidden) +{ + mg_metal_surface* surface = (mg_metal_surface*)interface; + @autoreleasepool + { + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + [surface->metalLayer setHidden:hidden]; + [CATransaction commit]; + } +} + +bool mg_metal_surface_get_hidden(mg_surface_data* interface) +{ + mg_metal_surface* surface = (mg_metal_surface*)interface; + @autoreleasepool + { + return([surface->metalLayer isHidden]); + } +} + +//TODO fix that according to real scaling, depending on the monitor settings +static const f32 MG_METAL_SURFACE_CONTENTS_SCALING = 2; + +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 + { + 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.swapInterval = mg_metal_surface_swap_interval; + surface->interface.getFrame = mg_metal_surface_get_frame; + surface->interface.setFrame = mg_metal_surface_set_frame; + surface->interface.getHidden = mg_metal_surface_get_hidden; + surface->interface.setHidden = mg_metal_surface_set_hidden; + + @autoreleasepool + { + NSRect frame = [[windowData->osx.nsWindow contentView] frame]; + surface->view = [[NSView alloc] initWithFrame: frame]; + [surface->view setWantsLayer:YES]; + + [[windowData->osx.nsWindow contentView] addSubview: surface->view]; + + 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 setOpaque:NO]; + + surface->metalLayer.device = surface->device; + + //----------------------------------------------------------- + //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_data*)surface); + return(handle); + } +} + +void* mg_metal_surface_layer(mg_surface surface) +{ + mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); + if(surfaceData && surfaceData->backend == MG_BACKEND_METAL) + { + mg_metal_surface* metalSurface = (mg_metal_surface*)surfaceData; + return(metalSurface->metalLayer); + } + else + { + return(nil); + } +} + +void* mg_metal_surface_drawable(mg_surface surface) +{ + mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); + if(surfaceData && surfaceData->backend == MG_BACKEND_METAL) + { + mg_metal_surface* metalSurface = (mg_metal_surface*)surfaceData; + return(metalSurface->drawable); + } + else + { + return(nil); + } +} + +void* mg_metal_surface_command_buffer(mg_surface surface) +{ + mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); + if(surfaceData && surfaceData->backend == MG_BACKEND_METAL) + { + mg_metal_surface* metalSurface = (mg_metal_surface*)surfaceData; + return(metalSurface->commandBuffer); + } + else + { + return(nil); + } +} + + +#undef LOG_SUBSYSTEM diff --git a/src/win32_app.c b/src/win32_app.c index 2147839..6cd7015 100644 --- a/src/win32_app.c +++ b/src/win32_app.c @@ -516,6 +516,16 @@ mp_window mp_window_create(mp_rect rect, const char* title, mp_window_style styl goto quit; } + /* + //NOTE: get primary monitor dimensions + const POINT ptZero = { 0, 0 }; + HMONITOR monitor = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY); + MONITORINFO monitorInfo = {.cbSize = sizeof(MONITORINFO)}; + GetMonitorInfo(monitor, &monitorInfo); + RECT adjustRect = {rect.x, monitorInfo.rcMonitor.bottom - rect.y - rect.h, rect.w, rect.h}; + AdjustWindowRect(&adjustRect, WS_OVERLAPPEDWINDOW, false); + */ + HWND windowHandle = CreateWindow("ApplicationWindowClass", "Test Window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rect.w, rect.h, @@ -721,6 +731,21 @@ void mp_window_bring_to_front(mp_window window) } } +mp_rect mp_window_get_content_rect(mp_window window) +{ + mp_rect rect = {0}; + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + RECT winRect; + if(GetClientRect(windowData->win32.hWnd, &winRect)) + { + rect = (mp_rect){0, 0, winRect.right - winRect.left, winRect.bottom - winRect.top}; + } + } + return(rect); +} + /////////////////////////////////////////// WIP /////////////////////////////////////////////// //TODO: this is thrown here for a quick test. We should: // - check for errors diff --git a/src/win32_gl_surface.c b/src/win32_gl_surface.c index f732cdc..ce5120f 100644 --- a/src/win32_gl_surface.c +++ b/src/win32_gl_surface.c @@ -39,10 +39,15 @@ void mg_gl_surface_prepare(mg_surface_data* interface) void mg_gl_surface_present(mg_surface_data* interface) { mg_gl_surface* surface = (mg_gl_surface*)interface; - SwapBuffers(surface->hDC); } +void mg_gl_surface_swap_interval(mg_surface_data* interface, int swap) +{ + mg_gl_surface* surface = (mg_gl_surface*)interface; + wglSwapIntervalEXT(swap); +} + mp_rect mg_gl_surface_get_frame(mg_surface_data* interface) { mg_gl_surface* surface = (mg_gl_surface*)interface; @@ -186,9 +191,7 @@ mg_surface mg_gl_surface_create_for_window(mp_window window) //NOTE: make gl context current wglMakeCurrent(hDC, glContext); - - //TODO: set this back to 1 after testing - wglSwapIntervalEXT(0); + wglSwapIntervalEXT(1); //TODO save important info in surface_data and return a handle mg_gl_surface* surface = malloc_type(mg_gl_surface); @@ -196,6 +199,7 @@ mg_surface mg_gl_surface_create_for_window(mp_window window) surface->interface.destroy = mg_gl_surface_destroy; surface->interface.prepare = mg_gl_surface_prepare; surface->interface.present = mg_gl_surface_present; + surface->interface.swapInterval = mg_gl_surface_swap_interval; surface->interface.getFrame = mg_gl_surface_get_frame; //TODO: get/set frame/hidden diff --git a/todo.txt b/todo.txt index ca682c4..1561ea8 100644 --- a/todo.txt +++ b/todo.txt @@ -3,17 +3,22 @@ Canvas renderer perf -------------------- [.] Perf [x] Split vertex data into per-vertex and per-shape data. Keep only pos, cubics, and shapeID in vertex data - [>] make zIndex implicit when calling push_vertex - [>] rename zIndex with "shapeIndex" everywhere - [>] remove color args in functions that don't need it anymore [?] use half-floats or short fixed-point for pos and uv, packing them in two ints [?] pre-compute triangle edges/bounding boxes? -[ ] Use clip rects +[>] Add surface scaling for high dpi surfaces + +[x] Allow setting swap interval + [!] Allow swap interval of 0 on macos + +[>] Use clip rects in tiling/drawing pass + +[ ] Clean canvas code + [ ] make zIndex implicit when calling push_vertex + [ ] rename zIndex with "shapeIndex" everywhere + [ ] remove color args in functions that don't need it anymore + [ ] Clean shaders (folder/filenames, version string, debug flags, ...) -[ ] Correctly handle surface size -[ ] Add surface scaling for high dpi surfaces -[ ] Allow setting swap interval [ ] Fix resource loading path in examples (to load font relative to executable)