Allow setting surface swap interval in opengl surface

This commit is contained in:
martinfouilleul 2023-02-08 11:49:00 +01:00
parent c982b524c0
commit e6e674ee04
8 changed files with 609 additions and 535 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -1,299 +1,309 @@
/************************************************************//**
*
* @file: graphics.m
* @author: Martin Fouilleul
* @date: 12/07/2020
* @revision:
*
*****************************************************************/
#import<Metal/Metal.h>
#import <QuartzCore/QuartzCore.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_data 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_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<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_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<MTLRenderCommandEncoder> 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<Metal/Metal.h>
#import <QuartzCore/QuartzCore.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_data 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_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<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_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<MTLRenderCommandEncoder> 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

View File

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

View File

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

View File

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