- Conflating mp_views and surfaces

- Implicit canvas context in canvas API
- GLES window surface on mac
This commit is contained in:
Martin Fouilleul 2023-01-28 17:24:43 +01:00
parent 9bf62d2218
commit bd7e1a15f1
20 changed files with 2497 additions and 2609 deletions

11
examples/canvas/build.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/bash
BINDIR=../../bin
RESDIR=../../resources
SRCDIR=../../src
INCLUDES="-I$SRCDIR -I$SRCDIR/util -I$SRCDIR/platform -I$SRCDIR/app"
LIBS="-L$BINDIR -lmilepost -framework Carbon -framework Cocoa -framework Metal -framework QuartzCore"
FLAGS="-mmacos-version-min=10.15.4 -DDEBUG -DLOG_COMPILE_DEBUG -Wl,-dead_strip"
clang -g $FLAGS $LIBS $INCLUDES -o $BINDIR/example_canvas main.c

144
examples/canvas/main.c Normal file
View File

@ -0,0 +1,144 @@
/************************************************************//**
*
* @file: main.cpp
* @author: Martin Fouilleul
* @date: 30/07/2022
* @revision:
*
*****************************************************************/
#include<stdlib.h>
#include<string.h>
#define _USE_MATH_DEFINES //NOTE: necessary for MSVC
#include<math.h>
#include"milepost.h"
#include"metal_surface.h"
#define LOG_SUBSYSTEM "Main"
int main()
{
LogLevel(LOG_LEVEL_DEBUG);
mp_init();
mp_rect rect = {.x = 100, .y = 100, .w = 800, .h = 600};
mp_window window = mp_window_create(rect, "test", 0);
//NOTE: create surface
mg_surface surface = mg_metal_surface_create_for_window(window);
//TODO: create canvas
mg_canvas canvas = mg_canvas_create(surface);
// start app
mp_window_bring_to_front(window);
mp_window_focus(window);
while(!mp_should_quit())
{
mp_pump_events(0);
mp_event event = {0};
while(mp_next_event(&event))
{
switch(event.type)
{
case MP_EVENT_WINDOW_CLOSE:
{
mp_request_quit();
} break;
case MP_EVENT_WINDOW_RESIZE:
{
printf("resized, rect = {%f, %f, %f, %f}\n",
event.frame.rect.x,
event.frame.rect.y,
event.frame.rect.w,
event.frame.rect.h);
} break;
case MP_EVENT_WINDOW_MOVE:
{
printf("moved, rect = {%f, %f, %f, %f}\n",
event.frame.rect.x,
event.frame.rect.y,
event.frame.rect.w,
event.frame.rect.h);
} break;
case MP_EVENT_MOUSE_MOVE:
{
printf("mouse moved, pos = {%f, %f}, delta = {%f, %f}\n",
event.move.x,
event.move.y,
event.move.deltaX,
event.move.deltaY);
} break;
case MP_EVENT_MOUSE_WHEEL:
{
printf("mouse wheel, delta = {%f, %f}\n",
event.move.deltaX,
event.move.deltaY);
} break;
case MP_EVENT_MOUSE_ENTER:
{
printf("mouse enter\n");
} break;
case MP_EVENT_MOUSE_LEAVE:
{
printf("mouse leave\n");
} break;
case MP_EVENT_MOUSE_BUTTON:
{
printf("mouse button %i: %i\n",
event.key.code,
event.key.action == MP_KEY_PRESS ? 1 : 0);
} break;
case MP_EVENT_KEYBOARD_KEY:
{
printf("key %i: %s\n",
event.key.code,
event.key.action == MP_KEY_PRESS ? "press" : (event.key.action == MP_KEY_RELEASE ? "release" : "repeat"));
} break;
case MP_EVENT_KEYBOARD_CHAR:
{
printf("entered char %s\n", event.character.sequence);
} break;
default:
break;
}
}
mg_surface_prepare(surface);
mg_set_color_rgba(1, 0, 1, 1);
mg_clear();
mg_set_color_rgba(1, 1, 0, 1);
mg_circle_fill(400, 300, 200);
mg_set_color_rgba(0, 0, 0, 1);
mg_set_width(20);
mg_move_to(300, 200);
mg_cubic_to(350, 150, 450, 150, 500, 200);
mg_stroke();
mg_ellipse_fill(330, 350, 30, 50);
mg_ellipse_fill(470, 350, 30, 50);
mg_flush();
mg_surface_present(surface);
}
mp_terminate();
return(0);
}

11
examples/triangleGL/build.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/bash
BINDIR=../../bin
RESDIR=../../resources
SRCDIR=../../src
INCLUDES="-I$SRCDIR -I$SRCDIR/util -I$SRCDIR/platform -I$SRCDIR/app"
LIBS="-L$BINDIR -lmilepost -framework Carbon -framework Cocoa -framework Metal -framework QuartzCore"
FLAGS="-mmacos-version-min=10.15.4 -DDEBUG -DLOG_COMPILE_DEBUG"
clang -g $FLAGS $LIBS $INCLUDES -o test main.c

View File

@ -0,0 +1,2 @@
set INCLUDES=/I ..\..\src /I ..\..\src\util /I ..\..\src\platform /I ../../ext
cl /we4013 /Zi /Zc:preprocessor /std:c11 %INCLUDES% main.c /link /LIBPATH:../../bin milepost.lib user32.lib opengl32.lib gdi32.lib /out:test.exe

15
examples/triangleGLES/build.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
BINDIR=../../bin
RESDIR=../../resources
SRCDIR=../../src
INCLUDES="-I$SRCDIR -I$SRCDIR/util -I$SRCDIR/platform -I$SRCDIR/app -I$SRCDIR/../ext/angle_headers"
LIBS="-L$BINDIR -lmilepost -framework Carbon -framework Cocoa -framework Metal -framework QuartzCore -L$RESDIR -lGLESv2 -lEGL"
FLAGS="-mmacos-version-min=10.15.4 -DDEBUG -DLOG_COMPILE_DEBUG"
clang -g $FLAGS $LIBS $INCLUDES -o $BINDIR/example_gles_triangle main.c
# change dynamic libraries install name
install_name_tool -change "./libEGL.dylib" "@loader_path/../resources/libEGL.dylib" $BINDIR/example_gles_triangle
install_name_tool -change "./libGLESv2.dylib" "@loader_path/../resources/libGLESv2.dylib" $BINDIR/example_gles_triangle

View File

@ -0,0 +1,232 @@
/************************************************************//**
*
* @file: main.cpp
* @author: Martin Fouilleul
* @date: 30/07/2022
* @revision:
*
*****************************************************************/
#include<stdlib.h>
#include<string.h>
#define _USE_MATH_DEFINES //NOTE: necessary for MSVC
#include<math.h>
#include<GLES3/gl32.h>
#include"milepost.h"
#define LOG_SUBSYSTEM "Main"
mg_surface mg_gles_surface_create_for_window(mp_window window);
unsigned int program;
const char* vshaderSource =
//"#version 320 es\n"
"attribute vec4 vPosition;\n"
"uniform mat4 transform;\n"
"void main()\n"
"{\n"
" gl_Position = transform*vPosition;\n"
"}\n";
const char* fshaderSource =
//"#version 320 es\n"
"precision mediump float;\n"
"void main()\n"
"{\n"
" gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
"}\n";
void compile_shader(GLuint shader, const char* source)
{
glShaderSource(shader, 1, &source, 0);
glCompileShader(shader);
int err = glGetError();
if(err)
{
printf("gl error: %i\n", err);
}
int status = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if(!status)
{
char buffer[256];
int size = 0;
glGetShaderInfoLog(shader, 256, &size, buffer);
printf("shader error: %.*s\n", size, buffer);
}
}
int main()
{
LogLevel(LOG_LEVEL_DEBUG);
mp_init();
mp_rect rect = {.x = 100, .y = 100, .w = 800, .h = 600};
mp_window window = mp_window_create(rect, "test", 0);
//NOTE: create surface
mg_surface surface = mg_gles_surface_create_for_window(window);
//NOTE: init shader and gl state
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
GLuint vertexBuffer;
glGenBuffers(1, &vertexBuffer);
GLfloat vertices[] = {
-0.866/2, -0.5/2, 0, 0.866/2, -0.5/2, 0, 0, 0.5, 0};
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
unsigned int vshader = glCreateShader(GL_VERTEX_SHADER);
unsigned int fshader = glCreateShader(GL_FRAGMENT_SHADER);
program = glCreateProgram();
compile_shader(vshader, vshaderSource);
compile_shader(fshader, fshaderSource);
glAttachShader(program, vshader);
glAttachShader(program, fshader);
glLinkProgram(program);
int status = 0;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if(!status)
{
char buffer[256];
int size = 0;
glGetProgramInfoLog(program, 256, &size, buffer);
printf("link error: %.*s\n", size, buffer);
}
glUseProgram(program);
mp_window_bring_to_front(window);
// mp_window_focus(window);
while(!mp_should_quit())
{
mp_pump_events(0);
mp_event event = {0};
while(mp_next_event(&event))
{
switch(event.type)
{
case MP_EVENT_WINDOW_CLOSE:
{
mp_request_quit();
} break;
case MP_EVENT_WINDOW_RESIZE:
{
printf("resized, rect = {%f, %f, %f, %f}\n",
event.frame.rect.x,
event.frame.rect.y,
event.frame.rect.w,
event.frame.rect.h);
} break;
case MP_EVENT_WINDOW_MOVE:
{
printf("moved, rect = {%f, %f, %f, %f}\n",
event.frame.rect.x,
event.frame.rect.y,
event.frame.rect.w,
event.frame.rect.h);
} break;
case MP_EVENT_MOUSE_MOVE:
{
printf("mouse moved, pos = {%f, %f}, delta = {%f, %f}\n",
event.move.x,
event.move.y,
event.move.deltaX,
event.move.deltaY);
} break;
case MP_EVENT_MOUSE_WHEEL:
{
printf("mouse wheel, delta = {%f, %f}\n",
event.move.deltaX,
event.move.deltaY);
} break;
case MP_EVENT_MOUSE_ENTER:
{
printf("mouse enter\n");
} break;
case MP_EVENT_MOUSE_LEAVE:
{
printf("mouse leave\n");
} break;
case MP_EVENT_MOUSE_BUTTON:
{
printf("mouse button %i: %i\n",
event.key.code,
event.key.action == MP_KEY_PRESS ? 1 : 0);
} break;
case MP_EVENT_KEYBOARD_KEY:
{
printf("key %i: %s\n",
event.key.code,
event.key.action == MP_KEY_PRESS ? "press" : (event.key.action == MP_KEY_RELEASE ? "release" : "repeat"));
} break;
case MP_EVENT_KEYBOARD_CHAR:
{
printf("entered char %s\n", event.character.sequence);
} break;
default:
break;
}
}
mg_surface_prepare(surface);
glClearColor(0.3, 0.3, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
static float alpha = 0;
//f32 aspect = frameSize.x/frameSize.y;
f32 aspect = 800/(f32)600;
glViewport(0, 0, 800, 600);
GLfloat matrix[] = {cosf(alpha)/aspect, sinf(alpha), 0, 0,
-sinf(alpha)/aspect, cosf(alpha), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1};
alpha += 2*M_PI/120;
glUniformMatrix4fv(0, 1, false, matrix);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glDrawArrays(GL_TRIANGLES, 0, 3);
mg_surface_present(surface);
}
mp_terminate();
return(0);
}

View File

@ -36,8 +36,7 @@ int main()
mp_window window = mp_window_create(rect, "test", 0);
//NOTE: create surface
mg_init();
mg_surface surface = mg_surface_create_for_window(window, MG_BACKEND_METAL);
mg_surface surface = mg_metal_surface_create_for_window(window);
//NOTE(martin): load the library
id<MTLDevice> device = MTLCreateSystemDefaultDevice();

File diff suppressed because it is too large Load Diff

View File

@ -2,72 +2,33 @@
*
* @file: graphics.h
* @author: Martin Fouilleul
* @date: 01/08/2022
* @date: 23/01/2023
* @revision:
*
*****************************************************************/
#ifndef __GRAPHICS_H_
#define __GRAPHICS_H_
#include"mp_app.h"
#ifdef __cplusplus
extern "C" {
#endif
//------------------------------------------------------------------------------------------
//NOTE(martin): graphics surface
//------------------------------------------------------------------------------------------
typedef struct mg_surface { u64 h; } mg_surface;
typedef enum { MG_BACKEND_DUMMY,
MG_BACKEND_METAL,
MG_BACKEND_GL,
MG_BACKEND_GLES,
//...
} mg_backend_id;
void mg_init();
mg_surface mg_surface_nil();
bool mg_surface_is_nil(mg_surface surface);
mg_surface mg_surface_create_for_window(mp_window window, mg_backend_id backend);
mg_surface mg_surface_create_offscreen(mg_backend_id backend, u32 width, u32 height);
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);
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);
vec2 mg_surface_size(mg_surface surface);
mp_rect mg_surface_frame(mg_surface surface);
//------------------------------------------------------------------------------------------
//NOTE(martin): graphics surface sharing
//NOTE(martin): graphics canvas structs
//------------------------------------------------------------------------------------------
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);
mg_surface_server mg_surface_server_create_native(void* p);
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);
//------------------------------------------------------------------------------------------
//NOTE(martin): canvas drawing 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;
@ -77,10 +38,6 @@ typedef struct mg_mat2x3
f32 m[6];
} mg_mat2x3;
typedef enum { MG_COORDS_2D_DISPLAY_CENTER,
MG_COORDS_2D_DISPLAY_TOP_LEFT,
MG_COORDS_2D_DISPLAY_BOTTOM_LEFT } mg_coordinate_system;
typedef struct mg_color
{
union
@ -126,19 +83,86 @@ typedef struct mg_text_extents
} mg_text_extents;
//------------------------------------------------------------------------------------------
//NOTE(martin): canvas lifetime and command streams
//NOTE(martin): graphics canvas
//------------------------------------------------------------------------------------------
mg_canvas mg_canvas_create(mg_surface surface, mp_rect viewPort);
void mg_canvas_destroy(mg_canvas context);
void mg_canvas_viewport(mg_canvas, mp_rect viewPort);
void mg_canvas_flush(mg_canvas context);
mg_stream mg_stream_create(mg_canvas context);
mg_stream mg_stream_swap(mg_canvas context, mg_stream stream);
void mg_stream_append(mg_canvas context, mg_stream stream);
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): fonts management
//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);
@ -165,78 +189,6 @@ int mg_font_get_glyph_extents(mg_font font, str32 glyphIndices, mg_text_extents*
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): matrix settings
//------------------------------------------------------------------------------------------
void mg_matrix_push(mg_canvas context, mg_mat2x3 matrix);
void mg_matrix_pop(mg_canvas context);
//------------------------------------------------------------------------------------------
//NOTE(martin): clipping
//------------------------------------------------------------------------------------------
void mg_clip_push(mg_canvas context, f32 x, f32 y, f32 w, f32 h);
void mg_clip_pop(mg_canvas context);
//------------------------------------------------------------------------------------------
//NOTE(martin): graphics attributes setting/getting
//------------------------------------------------------------------------------------------
void mg_set_clear_color(mg_canvas context, mg_color color);
void mg_set_clear_color_rgba(mg_canvas context, f32 r, f32 g, f32 b, f32 a);
void mg_set_color(mg_canvas context, mg_color color);
void mg_set_color_rgba(mg_canvas context, f32 r, f32 g, f32 b, f32 a);
void mg_set_width(mg_canvas context, f32 width);
void mg_set_tolerance(mg_canvas context, f32 tolerance);
void mg_set_joint(mg_canvas context, mg_joint_type joint);
void mg_set_max_joint_excursion(mg_canvas context, f32 maxJointExcursion);
void mg_set_cap(mg_canvas context, mg_cap_type cap);
void mg_set_font(mg_canvas context, mg_font font);
void mg_set_font_size(mg_canvas context, f32 size);
void mg_set_text_flip(mg_canvas context, bool flip);
mg_color mg_get_clear_color(mg_canvas context);
mg_color mg_get_color(mg_canvas context);
f32 mg_get_width(mg_canvas context);
f32 mg_get_tolerance(mg_canvas context);
mg_joint_type mg_get_joint(mg_canvas context);
f32 mg_get_max_joint_excursion(mg_canvas context);
mg_cap_type mg_get_cap(mg_canvas context);
mg_font mg_get_font(mg_canvas context);
f32 mg_get_font_size(mg_canvas context);
bool mg_get_text_flip(mg_canvas context);
//------------------------------------------------------------------------------------------
//NOTE(martin): path construction
//------------------------------------------------------------------------------------------
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);
void mg_cubic_to(mg_canvas context, f32 x1, f32 y1, f32 x2, f32 y2, f32 x3, f32 y3);
void mg_close_path(mg_canvas context);
mp_rect mg_glyph_outlines(mg_canvas context, str32 glyphIndices);
void mg_codepoints_outlines(mg_canvas context, str32 string);
void mg_text_outlines(mg_canvas context, str8 string);
//------------------------------------------------------------------------------------------
//NOTE(martin): clear/fill/stroke
//------------------------------------------------------------------------------------------
void mg_clear(mg_canvas context);
void mg_fill(mg_canvas context);
void mg_stroke(mg_canvas context);
//------------------------------------------------------------------------------------------
//NOTE(martin): 'fast' shapes primitives
//------------------------------------------------------------------------------------------
void mg_rectangle_fill(mg_canvas context, f32 x, f32 y, f32 w, f32 h);
void mg_rectangle_stroke(mg_canvas context, f32 x, f32 y, f32 w, f32 h);
void mg_rounded_rectangle_fill(mg_canvas context, f32 x, f32 y, f32 w, f32 h, f32 r);
void mg_rounded_rectangle_stroke(mg_canvas context, f32 x, f32 y, f32 w, f32 h, f32 r);
void mg_ellipse_fill(mg_canvas context, f32 x, f32 y, f32 rx, f32 ry);
void mg_ellipse_stroke(mg_canvas context, f32 x, f32 y, f32 rx, f32 ry);
void mg_circle_fill(mg_canvas context, f32 x, f32 y, f32 r);
void mg_circle_stroke(mg_canvas context, f32 x, f32 y, f32 r);
void mg_arc(mg_canvas handle, f32 x, f32 y, f32 r, f32 arcAngle, f32 startAngle);
//------------------------------------------------------------------------------------------
//NOTE(martin): images
//------------------------------------------------------------------------------------------
@ -245,17 +197,14 @@ 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);
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_canvas canvas, mg_image image);
void mg_image_drestroy(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);
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);
#ifdef __cplusplus
} // extern "C"
#endif
#endif //__GRAPHICS_H_

View File

@ -2,7 +2,7 @@
*
* @file: graphics_internal.h
* @author: Martin Fouilleul
* @date: 01/08/2022
* @date: 23/01/2023
* @revision:
*
*****************************************************************/
@ -15,73 +15,183 @@
extern "C" {
#endif
//---------------------------------------------------------------------------------------------
// Surfaces
//---------------------------------------------------------------------------------------------
typedef struct mg_surface_info mg_surface_info;
typedef enum { MG_BACKEND_DUMMY,
MG_BACKEND_METAL,
MG_BACKEND_GL,
MG_BACKEND_GLES,
//...
} mg_backend_id;
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_data mg_surface_data;
typedef struct mg_surface_info
typedef void (*mg_surface_destroy_proc)(mg_surface_data* surface);
typedef void (*mg_surface_prepare_proc)(mg_surface_data* surface);
typedef void (*mg_surface_present_proc)(mg_surface_data* surface);
typedef mp_rect (*mg_surface_get_frame_proc)(mg_surface_data* surface);
typedef void (*mg_surface_set_frame_proc)(mg_surface_data* surface, mp_rect frame);
typedef bool (*mg_surface_get_hidden_proc)(mg_surface_data* surface);
typedef void (*mg_surface_set_hidden_proc)(mg_surface_data* surface, bool hidden);
typedef struct mg_surface_data
{
mg_backend_id backend;
mg_surface_destroy_proc destroy;
mg_surface_prepare_proc prepare;
mg_surface_present_proc present;
mg_surface_resize_proc resize;
mg_surface_get_frame_proc getFrame;
mg_surface_set_frame_proc setFrame;
mg_surface_get_hidden_proc getHidden;
mg_surface_set_hidden_proc setHidden;
mg_surface_get_size_proc getSize;
mg_surface_get_os_resource_proc getOSResource;
} mg_surface_info;
} mg_surface_data;
mg_surface mg_surface_alloc_handle(mg_surface_info* surface);
mg_surface_info* mg_surface_ptr_from_handle(mg_surface handle);
//---------------------------------------------------------------------------------------------
// Surface servers
//---------------------------------------------------------------------------------------------
typedef struct mg_surface_server_info mg_surface_server_info;
mg_surface mg_surface_alloc_handle(mg_surface_data* surface);
mg_surface_data* mg_surface_data_from_handle(mg_surface handle);
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);
//---------------------------------------------------------------
// graphics structs
//---------------------------------------------------------------
typedef enum { MG_PATH_MOVE,
MG_PATH_LINE,
MG_PATH_QUADRATIC,
MG_PATH_CUBIC } mg_path_elt_type;
typedef struct mg_surface_server_info
typedef struct mg_path_elt
{
mg_surface_server_destroy_proc destroy;
mg_surface_server_get_id_proc getID;
mg_path_elt_type type;
vec2 p[3];
} mg_surface_server_info;
} mg_path_elt;
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
typedef struct mg_path_descriptor
{
mg_surface_client_destroy_proc destroy;
mg_surface_client_attach_proc attach;
mg_surface_client_detach_proc detach;
u32 startIndex;
u32 count;
vec2 startPoint;
} mg_surface_client_info;
} mg_path_descriptor;
mg_surface_client mg_surface_client_alloc_handle(mg_surface_client_info* client);
//---------------------------------------------------------------------------------------------
// vertex layout
//---------------------------------------------------------------------------------------------
typedef struct mgc_vertex_layout
typedef struct mg_attributes
{
f32 width;
f32 tolerance;
mg_color color;
mg_joint_type joint;
f32 maxJointExcursion;
mg_cap_type cap;
mg_font font;
f32 fontSize;
mg_image image;
mp_rect clip;
} mg_attributes;
typedef struct mg_rounded_rect
{
f32 x;
f32 y;
f32 w;
f32 h;
f32 r;
} mg_rounded_rect;
typedef enum { MG_CMD_CLEAR = 0,
MG_CMD_FILL,
MG_CMD_STROKE,
MG_CMD_RECT_FILL,
MG_CMD_RECT_STROKE,
MG_CMD_ROUND_RECT_FILL,
MG_CMD_ROUND_RECT_STROKE,
MG_CMD_ELLIPSE_FILL,
MG_CMD_ELLIPSE_STROKE,
MG_CMD_JUMP,
MG_CMD_MATRIX_PUSH,
MG_CMD_MATRIX_POP,
MG_CMD_CLIP_PUSH,
MG_CMD_CLIP_POP,
MG_CMD_IMAGE_DRAW,
MG_CMD_ROUNDED_IMAGE_DRAW,
} mg_primitive_cmd;
typedef struct mg_primitive
{
mg_primitive_cmd cmd;
mg_attributes attributes;
union
{
mg_path_descriptor path;
mp_rect rect;
mg_rounded_rect roundedRect;
utf32 codePoint;
u32 jump;
mg_mat2x3 matrix;
};
} mg_primitive;
typedef struct mg_glyph_map_entry
{
unicode_range range;
u32 firstGlyphIndex;
} mg_glyph_map_entry;
typedef struct mg_glyph_data
{
bool exists;
utf32 codePoint;
mg_path_descriptor pathDescriptor;
mg_text_extents extents;
//...
} mg_glyph_data;
enum
{
MG_STREAM_MAX_COUNT = 128,
MG_IMAGE_MAX_COUNT = 128
};
typedef struct mg_image_data
{
list_elt listElt;
u32 generation;
mp_rect rect;
} mg_image_data;
enum
{
MG_MATRIX_STACK_MAX_DEPTH = 64,
MG_CLIP_STACK_MAX_DEPTH = 64,
MG_MAX_PATH_ELEMENT_COUNT = 2<<20,
MG_MAX_PRIMITIVE_COUNT = 8<<10
};
typedef struct mg_font_data
{
list_elt freeListElt;
u32 generation;
u32 rangeCount;
u32 glyphCount;
u32 outlineCount;
mg_glyph_map_entry* glyphMap;
mg_glyph_data* glyphs;
mg_path_elt* outlines;
f32 unitsPerEm;
mg_font_extents extents;
} mg_font_data;
typedef struct mg_vertex_layout
{
u32 maxVertexCount;
u32 maxIndexCount;
@ -107,26 +217,71 @@ typedef struct mgc_vertex_layout
void* indexBuffer;
u32 indexStride;
} mgc_vertex_layout;
} mg_vertex_layout;
typedef struct mg_canvas_painter mg_canvas_painter;
typedef void (*mgc_painter_destroy_proc)(mg_canvas_painter* painter);
typedef void (*mgc_painter_draw_buffers_proc)(mg_canvas_painter* painter, u32 vertexCount, u32 indexCount, mg_color clearColor);
typedef void (*mgc_painter_set_viewport_proc)(mg_canvas_painter* painter, mp_rect viewPort);
typedef void (*mgc_painter_atlas_upload_proc)(mg_canvas_painter* painter, mp_rect rect, u8* bytes);
typedef struct mg_canvas_backend mg_canvas_backend;
typedef struct mg_canvas_painter
typedef void (*mg_canvas_backend_destroy_proc)(mg_canvas_backend* backend);
typedef void (*mg_canvas_backend_draw_buffers_proc)(mg_canvas_backend* backend, u32 vertexCount, u32 indexCount, mg_color clearColor);
typedef void (*mg_canvas_backend_atlas_upload_proc)(mg_canvas_backend* backend, mp_rect rect, u8* bytes);
typedef struct mg_canvas_backend
{
mg_surface_info* surface;
mgc_vertex_layout vertexLayout;
mgc_painter_destroy_proc destroy;
mgc_painter_draw_buffers_proc drawBuffers;
mgc_painter_set_viewport_proc setViewPort;
mgc_painter_atlas_upload_proc atlasUpload;
mg_vertex_layout vertexLayout;
} mg_canvas_painter;
mg_canvas_backend_destroy_proc destroy;
mg_canvas_backend_draw_buffers_proc drawBuffers;
mg_canvas_backend_atlas_upload_proc atlasUpload;
} mg_canvas_backend;
typedef struct mg_canvas_data
{
list_elt freeListElt;
u32 generation;
u64 frameCounter;
mg_mat2x3 transform;
mp_rect clip;
mg_attributes attributes;
bool textFlip;
mg_path_elt pathElements[MG_MAX_PATH_ELEMENT_COUNT];
mg_path_descriptor path;
vec2 subPathStartPoint;
vec2 subPathLastPoint;
mg_mat2x3 matrixStack[MG_MATRIX_STACK_MAX_DEPTH];
u32 matrixStackSize;
mp_rect clipStack[MG_CLIP_STACK_MAX_DEPTH];
u32 clipStackSize;
u32 nextZIndex;
u32 primitiveCount;
mg_primitive primitives[MG_MAX_PRIMITIVE_COUNT];
u32 vertexCount;
u32 indexCount;
mg_image_data images[MG_IMAGE_MAX_COUNT];
u32 imageNextIndex;
list_info imageFreeList;
vec2 atlasPos;
u32 atlasLineHeight;
mg_image blankImage;
mg_canvas_backend* backend;
} mg_canvas_data;
enum
{
MG_ATLAS_SIZE = 8192,
};
#define MG_ATLAS_SIZE 8192
#ifdef __cplusplus
} // extern "C"

442
src/metal_canvas.m Normal file
View File

@ -0,0 +1,442 @@
/************************************************************//**
*
* @file: metal_canvas.m
* @author: Martin Fouilleul
* @date: 12/07/2020
* @revision: 24/01/2023
*
*****************************************************************/
#import<Metal/Metal.h>
#import<QuartzCore/CAMetalLayer.h>
#include<simd/simd.h>
#include"graphics_internal.h"
#include"macro_helpers.h"
#include"osx_app.h"
#include"metal_shader.h"
#define LOG_SUBSYSTEM "Graphics"
static const int MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH = 4<<20;
typedef struct mg_metal_canvas_backend
{
mg_canvas_backend interface;
mg_surface surface;
// permanent metal resources
id<MTLComputePipelineState> tilingPipeline;
id<MTLComputePipelineState> sortingPipeline;
id<MTLComputePipelineState> boxingPipeline;
id<MTLComputePipelineState> computePipeline;
id<MTLRenderPipelineState> renderPipeline;
mp_rect viewPort;
// textures and buffers
id<MTLTexture> outTexture;
id<MTLTexture> atlasTexture;
id<MTLBuffer> vertexBuffer;
id<MTLBuffer> indexBuffer;
id<MTLBuffer> tileCounters;
id<MTLBuffer> tilesArray;
id<MTLBuffer> triangleArray;
id<MTLBuffer> boxArray;
} mg_metal_canvas_backend;
mg_metal_surface* mg_metal_canvas_get_surface(mg_metal_canvas_backend* canvas)
{
mg_metal_surface* res = 0;
mg_surface_data* data = mg_surface_data_from_handle(canvas->surface);
if(data && data->backend == MG_BACKEND_METAL)
{
res = (mg_metal_surface*)data;
}
return(res);
}
void mg_metal_canvas_draw_buffers(mg_canvas_backend* interface, u32 vertexCount, u32 indexCount, mg_color clearColor)
{
mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface;
mg_metal_surface* surface = mg_metal_canvas_get_surface(backend);
if(!surface)
{
return;
}
@autoreleasepool
{
if(surface->commandBuffer == nil || surface->commandBuffer == nil)
{
mg_metal_surface_acquire_drawable_and_command_buffer(surface);
}
ASSERT(indexCount * sizeof(i32) < [backend->indexBuffer length]);
f32 scale = surface->metalLayer.contentsScale;
vector_uint2 viewportSize = {backend->viewPort.w * scale, backend->viewPort.h * scale};
//-----------------------------------------------------------
//NOTE(martin): encode the clear counter
//-----------------------------------------------------------
id<MTLBlitCommandEncoder> blitEncoder = [surface->commandBuffer blitCommandEncoder];
[blitEncoder fillBuffer: backend->tileCounters range: NSMakeRange(0, RENDERER_MAX_TILES*sizeof(uint)) value: 0];
[blitEncoder endEncoding];
//-----------------------------------------------------------
//NOTE(martin): encode the boxing pass
//-----------------------------------------------------------
id<MTLComputeCommandEncoder> boxEncoder = [surface->commandBuffer computeCommandEncoder];
[boxEncoder setComputePipelineState: backend->boxingPipeline];
[boxEncoder setBuffer: backend->vertexBuffer offset:0 atIndex: 0];
[boxEncoder setBuffer: backend->indexBuffer offset:0 atIndex: 1];
[boxEncoder setBuffer: backend->triangleArray offset:0 atIndex: 2];
[boxEncoder setBuffer: backend->boxArray offset:0 atIndex: 3];
[boxEncoder setBytes: &scale length: sizeof(float) atIndex: 4];
MTLSize boxGroupSize = MTLSizeMake(backend->boxingPipeline.maxTotalThreadsPerThreadgroup, 1, 1);
MTLSize boxGridSize = MTLSizeMake(indexCount/3, 1, 1);
[boxEncoder dispatchThreads: boxGridSize threadsPerThreadgroup: boxGroupSize];
[boxEncoder endEncoding];
//-----------------------------------------------------------
//NOTE(martin): encode the tiling pass
//-----------------------------------------------------------
id<MTLComputeCommandEncoder> tileEncoder = [surface->commandBuffer computeCommandEncoder];
[tileEncoder setComputePipelineState: backend->tilingPipeline];
[tileEncoder setBuffer: backend->boxArray offset:0 atIndex: 0];
[tileEncoder setBuffer: backend->tileCounters offset:0 atIndex: 1];
[tileEncoder setBuffer: backend->tilesArray offset:0 atIndex: 2];
[tileEncoder setBytes: &viewportSize length: sizeof(vector_uint2) atIndex: 3];
[tileEncoder dispatchThreads: boxGridSize threadsPerThreadgroup: boxGroupSize];
[tileEncoder endEncoding];
//-----------------------------------------------------------
//NOTE(martin): encode the sorting pass
//-----------------------------------------------------------
id<MTLComputeCommandEncoder> sortEncoder = [surface->commandBuffer computeCommandEncoder];
[sortEncoder setComputePipelineState: backend->sortingPipeline];
[sortEncoder setBuffer: backend->tileCounters offset:0 atIndex: 0];
[sortEncoder setBuffer: backend->triangleArray offset:0 atIndex: 1];
[sortEncoder setBuffer: backend->tilesArray offset:0 atIndex: 2];
[sortEncoder setBytes: &viewportSize length: sizeof(vector_uint2) atIndex: 3];
u32 nTilesX = (viewportSize.x + RENDERER_TILE_SIZE - 1)/RENDERER_TILE_SIZE;
u32 nTilesY = (viewportSize.y + RENDERER_TILE_SIZE - 1)/RENDERER_TILE_SIZE;
MTLSize sortGroupSize = MTLSizeMake(backend->boxingPipeline.maxTotalThreadsPerThreadgroup, 1, 1);
MTLSize sortGridSize = MTLSizeMake(nTilesX*nTilesY, 1, 1);
[sortEncoder dispatchThreads: sortGridSize threadsPerThreadgroup: sortGroupSize];
[sortEncoder endEncoding];
//-----------------------------------------------------------
//NOTE(martin): create compute encoder and encode commands
//-----------------------------------------------------------
vector_float4 clearColorVec4 = {clearColor.r, clearColor.g, clearColor.b, clearColor.a};
id<MTLComputeCommandEncoder> encoder = [surface->commandBuffer computeCommandEncoder];
[encoder setComputePipelineState:backend->computePipeline];
[encoder setTexture: backend->outTexture atIndex: 0];
[encoder setTexture: backend->atlasTexture atIndex: 1];
[encoder setBuffer: backend->vertexBuffer offset:0 atIndex: 0];
[encoder setBuffer: backend->tileCounters offset:0 atIndex: 1];
[encoder setBuffer: backend->tilesArray offset:0 atIndex: 2];
[encoder setBuffer: backend->triangleArray offset:0 atIndex: 3];
[encoder setBuffer: backend->boxArray offset:0 atIndex: 4];
[encoder setBytes: &clearColorVec4 length: sizeof(vector_float4) atIndex: 5];
//TODO: check that we don't exceed maxTotalThreadsPerThreadgroup
DEBUG_ASSERT(RENDERER_TILE_SIZE*RENDERER_TILE_SIZE <= backend->computePipeline.maxTotalThreadsPerThreadgroup);
MTLSize threadGridSize = MTLSizeMake(viewportSize.x, viewportSize.y, 1);
MTLSize threadGroupSize = MTLSizeMake(RENDERER_TILE_SIZE, RENDERER_TILE_SIZE, 1);
[encoder dispatchThreads: threadGridSize threadsPerThreadgroup:threadGroupSize];
[encoder endEncoding];
//-----------------------------------------------------------
//NOTE(martin): acquire drawable, create render encoder to blit texture to framebuffer
//-----------------------------------------------------------
MTLViewport viewport = {backend->viewPort.x * scale,
backend->viewPort.y * scale,
backend->viewPort.w * scale,
backend->viewPort.h * scale,
0,
1};
MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
renderPassDescriptor.colorAttachments[0].texture = surface->drawable.texture;
renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
id<MTLRenderCommandEncoder> renderEncoder = [surface->commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
[renderEncoder setViewport: viewport];
[renderEncoder setRenderPipelineState: backend->renderPipeline];
[renderEncoder setFragmentTexture: backend->outTexture atIndex: 0];
[renderEncoder drawPrimitives: MTLPrimitiveTypeTriangle
vertexStart: 0
vertexCount: 3 ];
[renderEncoder endEncoding];
}
}
/*
void mg_metal_canvas_viewport(mg_canvas_backend* interface, mp_rect viewPort)
{
mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface;
mg_metal_surface* surface = mg_metal_canvas_get_surface(backend);
if(!surface)
{
return;
}
backend->viewPort = viewPort;
@autoreleasepool
{
f32 scale = surface->metalLayer.contentsScale;
CGSize drawableSize = (CGSize){.width = viewPort.w * scale, .height = viewPort.h * scale};
[backend->outTexture release];
MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init];
texDesc.textureType = MTLTextureType2D;
texDesc.storageMode = MTLStorageModePrivate;
texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
texDesc.pixelFormat = MTLPixelFormatBGRA8Unorm;// MTLPixelFormatBGRA8Unorm_sRGB;
texDesc.width = drawableSize.width;
texDesc.height = drawableSize.height;
backend->outTexture = [surface->device newTextureWithDescriptor:texDesc];
}
}
*/
void mg_metal_canvas_update_vertex_layout(mg_metal_canvas_backend* backend)
{
char* vertexBase = (char*)[backend->vertexBuffer contents];
backend->interface.vertexLayout = (mg_vertex_layout){
.maxVertexCount = MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH,
.maxIndexCount = MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH,
.posBuffer = vertexBase + offsetof(mg_vertex, pos),
.posStride = sizeof(mg_vertex),
.cubicBuffer = vertexBase + offsetof(mg_vertex, cubic),
.cubicStride = sizeof(mg_vertex),
.uvBuffer = vertexBase + offsetof(mg_vertex, uv),
.uvStride = sizeof(mg_vertex),
.colorBuffer = vertexBase + offsetof(mg_vertex, color),
.colorStride = sizeof(mg_vertex),
.zIndexBuffer = vertexBase + offsetof(mg_vertex, zIndex),
.zIndexStride = sizeof(mg_vertex),
.clipsBuffer = vertexBase + offsetof(mg_vertex, clip),
.clipsStride = sizeof(mg_vertex),
.indexBuffer = [backend->indexBuffer contents],
.indexStride = sizeof(int)};
}
void mg_metal_canvas_destroy(mg_canvas_backend* interface)
{
mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface;
@autoreleasepool
{
[backend->outTexture release];
[backend->atlasTexture release];
[backend->vertexBuffer release];
[backend->indexBuffer release];
[backend->tilesArray release];
[backend->triangleArray release];
[backend->boxArray release];
[backend->computePipeline release];
}
}
void mg_metal_canvas_atlas_upload(mg_canvas_backend* interface, mp_rect rect, u8* bytes)
{@autoreleasepool{
mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface;
MTLRegion region = MTLRegionMake2D(rect.x, rect.y, rect.w, rect.h);
[backend->atlasTexture replaceRegion:region
mipmapLevel:0
withBytes:(void*)bytes
bytesPerRow: 4 * rect.w];
}}
mg_canvas_backend* mg_metal_canvas_create(mg_surface surface)
{
mg_metal_canvas_backend* backend = 0;
mg_surface_data* surfaceData = mg_surface_data_from_handle(surface);
if(surfaceData && surfaceData->backend == MG_BACKEND_METAL)
{
mg_metal_surface* metalSurface = (mg_metal_surface*)surfaceData;
backend = malloc_type(mg_metal_canvas_backend);
backend->surface = surface;
//NOTE(martin): setup interface functions
backend->interface.destroy = mg_metal_canvas_destroy;
backend->interface.drawBuffers = mg_metal_canvas_draw_buffers;
backend->interface.atlasUpload = mg_metal_canvas_atlas_upload;
mp_rect frame = mg_surface_get_frame(surface);
backend->viewPort = (mp_rect){0, 0, frame.w, frame.h};
@autoreleasepool
{
f32 scale = metalSurface->metalLayer.contentsScale;
CGSize drawableSize = (CGSize){.width = backend->viewPort.w * scale, .height = backend->viewPort.h * scale};
//-----------------------------------------------------------
//NOTE(martin): create our output texture
//-----------------------------------------------------------
MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init];
texDesc.textureType = MTLTextureType2D;
texDesc.storageMode = MTLStorageModePrivate;
texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
texDesc.pixelFormat = MTLPixelFormatBGRA8Unorm;// MTLPixelFormatBGRA8Unorm_sRGB;
texDesc.width = drawableSize.width;
texDesc.height = drawableSize.height;
backend->outTexture = [metalSurface->device newTextureWithDescriptor:texDesc];
//TODO(martin): retain ?
//-----------------------------------------------------------
//NOTE(martin): create our atlas texture
//-----------------------------------------------------------
texDesc.textureType = MTLTextureType2D;
texDesc.storageMode = MTLStorageModeManaged;
texDesc.usage = MTLTextureUsageShaderRead;
texDesc.pixelFormat = MTLPixelFormatRGBA8Unorm; //MTLPixelFormatBGRA8Unorm;
texDesc.width = MG_ATLAS_SIZE;
texDesc.height = MG_ATLAS_SIZE;
backend->atlasTexture = [metalSurface->device newTextureWithDescriptor:texDesc];
//-----------------------------------------------------------
//NOTE(martin): create buffers for vertex and index
//-----------------------------------------------------------
MTLResourceOptions bufferOptions = MTLResourceCPUCacheModeWriteCombined
| MTLResourceStorageModeShared;
backend->indexBuffer = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(int)
options: bufferOptions];
backend->vertexBuffer = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(mg_vertex)
options: bufferOptions];
backend->tilesArray = [metalSurface->device newBufferWithLength: RENDERER_TILE_BUFFER_SIZE*sizeof(int)*RENDERER_MAX_TILES
options: MTLResourceStorageModePrivate];
backend->triangleArray = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(mg_triangle_data)
options: MTLResourceStorageModePrivate];
backend->boxArray = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(vector_float4)
options: MTLResourceStorageModePrivate];
//TODO(martin): retain ?
//-----------------------------------------------------------
//NOTE(martin): create and initialize tile counters
//-----------------------------------------------------------
backend->tileCounters = [metalSurface->device newBufferWithLength: RENDERER_MAX_TILES*sizeof(uint)
options: MTLResourceStorageModePrivate];
id<MTLCommandBuffer> commandBuffer = [metalSurface->commandQueue commandBuffer];
id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];
[blitEncoder fillBuffer: backend->tileCounters range: NSMakeRange(0, RENDERER_MAX_TILES*sizeof(uint)) value: 0];
[blitEncoder endEncoding];
[commandBuffer commit];
//-----------------------------------------------------------
//NOTE(martin): load the library
//-----------------------------------------------------------
//TODO(martin): filepath magic to find metallib path when not in the working directory
str8 shaderPath = mp_app_get_resource_path(mem_scratch(), "../resources/metal_shader.metallib");
NSString* metalFileName = [[NSString alloc] initWithBytes: shaderPath.ptr length:shaderPath.len encoding: NSUTF8StringEncoding];
NSError* err = 0;
id<MTLLibrary> library = [metalSurface->device newLibraryWithFile: metalFileName error:&err];
if(err != nil)
{
const char* errStr = [[err localizedDescription] UTF8String];
LOG_ERROR("error : %s\n", errStr);
return(0);
}
id<MTLFunction> tilingFunction = [library newFunctionWithName:@"TileKernel"];
id<MTLFunction> sortingFunction = [library newFunctionWithName:@"SortKernel"];
id<MTLFunction> boxingFunction = [library newFunctionWithName:@"BoundingBoxKernel"];
id<MTLFunction> computeFunction = [library newFunctionWithName:@"RenderKernel"];
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"VertexShader"];
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"FragmentShader"];
//-----------------------------------------------------------
//NOTE(martin): setup our data layout and pipeline state
//-----------------------------------------------------------
NSError* error = NULL;
backend->computePipeline = [metalSurface->device newComputePipelineStateWithFunction: computeFunction
error:&error];
ASSERT(backend->computePipeline);
MTLComputePipelineDescriptor* tilingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init];
tilingPipelineDesc.computeFunction = tilingFunction;
// tilingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
backend->tilingPipeline = [metalSurface->device newComputePipelineStateWithDescriptor: tilingPipelineDesc
options: MTLPipelineOptionNone
reflection: nil
error: &error];
MTLComputePipelineDescriptor* sortingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init];
sortingPipelineDesc.computeFunction = sortingFunction;
// sortingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
backend->sortingPipeline = [metalSurface->device newComputePipelineStateWithDescriptor: sortingPipelineDesc
options: MTLPipelineOptionNone
reflection: nil
error: &error];
MTLComputePipelineDescriptor* boxingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init];
boxingPipelineDesc.computeFunction = boxingFunction;
// boxingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
backend->boxingPipeline = [metalSurface->device newComputePipelineStateWithDescriptor: boxingPipelineDesc
options: MTLPipelineOptionNone
reflection: nil
error: &error];
//-----------------------------------------------------------
//NOTE(martin): setup our render pipeline state
//-----------------------------------------------------------
// create and initialize the pipeline state descriptor
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"My simple pipeline";
pipelineStateDescriptor.vertexFunction = vertexFunction;
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = metalSurface->metalLayer.pixelFormat;
// create render pipeline
backend->renderPipeline = [metalSurface->device newRenderPipelineStateWithDescriptor: pipelineStateDescriptor error:&err];
if(err != nil)
{
const char* errStr = [[err localizedDescription] UTF8String];
const char* descStr = [[err localizedFailureReason] UTF8String];
const char* recovStr = [[err localizedRecoverySuggestion] UTF8String];
LOG_ERROR("(%li) %s. %s. %s\n", [err code], errStr, descStr, recovStr);
return(0);
}
}
mg_metal_canvas_update_vertex_layout(backend);
}
return((mg_canvas_backend*)backend);
}
#undef LOG_SUBSYSTEM

View File

@ -1,417 +0,0 @@
/************************************************************//**
*
* @file: graphics.m
* @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];
}}
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
str8 shaderPath = mp_app_get_resource_path(mem_scratch(), "../resources/metal_shader.metallib");
NSString* metalFileName = [[NSString alloc] initWithBytes: shaderPath.ptr length:shaderPath.len encoding: NSUTF8StringEncoding];
NSError* err = 0;
id<MTLLibrary> library = [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

@ -14,9 +14,9 @@
#endif
mg_surface mg_metal_surface_create_for_window(mp_window window);
void* mg_metal_surface_render_encoder(mg_surface surface);
void* mg_metal_surface_compute_encoder(mg_surface surface);
void* mg_metal_surface_layer(mg_surface surface);
void* mg_metal_surface_drawable(mg_surface surface);
void* mg_metal_surface_command_buffer(mg_surface surface);

View File

@ -21,7 +21,7 @@ static const u32 MP_METAL_MAX_DRAWABLES_IN_FLIGHT = 3;
typedef struct mg_metal_surface
{
mg_surface_info interface;
mg_surface_data interface;
// permanent metal resources
NSView* view;
@ -37,6 +37,18 @@ typedef struct mg_metal_surface
} 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
@ -80,7 +92,7 @@ void mg_metal_surface_acquire_drawable_and_command_buffer(mg_metal_surface* surf
[surface->drawable retain];
}}
void mg_metal_surface_prepare(mg_surface_info* interface)
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);
@ -97,7 +109,7 @@ void mg_metal_surface_prepare(mg_surface_info* interface)
}
}
void mg_metal_surface_present(mg_surface_info* interface)
void mg_metal_surface_present(mg_surface_data* interface)
{
mg_metal_surface* surface = (mg_metal_surface*)interface;
@autoreleasepool
@ -115,34 +127,32 @@ void mg_metal_surface_present(mg_surface_info* interface)
}
}
void mg_metal_surface_destroy(mg_surface_info* interface)
void mg_metal_surface_set_frame(mg_surface_data* interface, mp_rect frame)
{
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];
CGRect cgFrame = {{frame.x, frame.y}, {frame.w, frame.h}};
[surface->view setFrame: cgFrame];
f32 scale = surface->metalLayer.contentsScale;
CGSize drawableSize = (CGSize){.width = width * scale, .height = height * scale};
CGSize drawableSize = (CGSize){.width = frame.w * scale, .height = frame.h * scale};
surface->metalLayer.drawableSize = drawableSize;
}
}
void mg_metal_surface_set_hidden(mg_surface_info* interface, bool hidden)
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
@ -154,24 +164,16 @@ void mg_metal_surface_set_hidden(mg_surface_info* interface, bool hidden)
}
}
vec2 mg_metal_surface_get_size(mg_surface_info* interface)
bool mg_metal_surface_get_hidden(mg_surface_data* 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});
return([surface->metalLayer isHidden]);
}
}
void* mg_metal_surface_get_os_resource(mg_surface_info* interface)
{
mg_metal_surface* surface = (mg_metal_surface*)interface;
return((void*)surface->metalLayer);
}
//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)
@ -190,10 +192,10 @@ mg_surface mg_metal_surface_create_for_window(mp_window window)
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.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;
surface->interface.getSize = mg_metal_surface_get_size;
surface->interface.getOSResource = mg_metal_surface_get_os_resource;
@autoreleasepool
{
@ -246,14 +248,14 @@ mg_surface mg_metal_surface_create_for_window(mp_window window)
surface->commandBuffer = nil;
}
mg_surface handle = mg_surface_alloc_handle((mg_surface_info*)surface);
mg_surface handle = mg_surface_alloc_handle((mg_surface_data*)surface);
return(handle);
}
}
void* mg_metal_surface_layer(mg_surface surface)
{
mg_surface_info* surfaceData = mg_surface_ptr_from_handle(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;
@ -267,7 +269,7 @@ void* mg_metal_surface_layer(mg_surface surface)
void* mg_metal_surface_drawable(mg_surface surface)
{
mg_surface_info* surfaceData = mg_surface_ptr_from_handle(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;
@ -281,7 +283,7 @@ void* mg_metal_surface_drawable(mg_surface surface)
void* mg_metal_surface_command_buffer(mg_surface surface)
{
mg_surface_info* surfaceData = mg_surface_ptr_from_handle(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;

View File

@ -46,6 +46,6 @@
// graphics/ui layer
//----------------------------------------------------------------
#include"graphics.h"
#include"ui.h"
//#include"ui.h"
#endif //__MILEPOST_H_

View File

@ -9,7 +9,7 @@
#include"osx_app.m"
#include"metal_surface.m"
#include"metal_painter.m"
#include"metal_canvas.m"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"

View File

@ -19,8 +19,9 @@
typedef struct mg_gles_surface
{
mg_surface_info interface;
mg_surface_data interface;
NSView* view;
CALayer* layer;
EGLDisplay eglDisplay;
EGLConfig eglConfig;

View File

@ -19,32 +19,59 @@
#include"osx_gles_surface.h"
void mg_gles_surface_prepare(mg_surface_info* interface)
void mg_gles_surface_destroy(mg_surface_data* interface)
{
//////////////////////////////////////////////////
//TODO
//////////////////////////////////////////////////
}
void mg_gles_surface_prepare(mg_surface_data* interface)
{
mg_gles_surface* surface = (mg_gles_surface*)interface;
eglMakeCurrent(surface->eglDisplay, surface->eglSurface, surface->eglSurface, surface->eglContext);
}
void mg_gles_surface_present(mg_surface_info* interface)
void mg_gles_surface_present(mg_surface_data* interface)
{
//TODO: eglSwapBuffers seem to never block in macOS (ie eglSwapInterval doesn't seem to have any effect)
// We need to use a CVDisplayLink to time this if we want surface present to block
mg_gles_surface* surface = (mg_gles_surface*)interface;
eglSwapBuffers(surface->eglDisplay, surface->eglSurface);
}
void mg_gles_surface_resize(mg_surface_info* interface, u32 width, u32 height)
void mg_gles_surface_set_frame(mg_surface_data* interface, mp_rect frame)
{
mg_gles_surface* surface = (mg_gles_surface*)interface;
f32 scale = surface->layer.contentsScale;
CGRect bounds = (CGRect){{0, 0}, {.width = width*scale, .height = height*scale}};
CGRect bounds = (CGRect){{frame.x * scale, frame.y * scale}, {.width = frame.w*scale, .height = frame.h*scale}};
[surface->layer setBounds: bounds];
}
void mg_gles_surface_set_hidden(mg_surface_info* interface, bool hidden)
mp_rect mg_gles_surface_get_frame(mg_surface_data* interface)
{
//NOTE: doesn't make sense for an offscreen surface?
mg_gles_surface* surface = (mg_gles_surface*)interface;
f32 scale = surface->layer.contentsScale;
CGRect bounds = [surface->layer bounds];
mp_rect rect = {bounds.origin.x / scale, bounds.origin.y / scale, bounds.size.width / scale, bounds.size.height / scale};
return(rect);
}
vec2 mg_gles_surface_get_size(mg_surface_info* interface)
void mg_gles_surface_set_hidden(mg_surface_data* interface, bool hidden)
{
//TODO: doesn't make sense for an offscreen surface?
}
bool mg_gles_surface_get_hidden(mg_surface_data* interface)
{
//TODO: doesn't make sense for an offscreen surface?
return(false);
}
vec2 mg_gles_surface_get_size(mg_surface_data* interface)
{
mg_gles_surface* surface = (mg_gles_surface*)interface;
CGRect bounds = [surface->layer bounds];
@ -53,39 +80,40 @@ vec2 mg_gles_surface_get_size(mg_surface_info* interface)
return(res);
}
void mg_gles_surface_destroy(mg_surface_info* interface)
{
//TODO
}
void* mg_gles_surface_get_os_resource(mg_surface_info* interface)
{
mg_gles_surface* surface = (mg_gles_surface*)interface;
return((void*)surface->layer);
}
mg_surface mg_gles_surface_create_offscreen(u32 width, u32 height)
mg_surface mg_gles_surface_create_with_view(u32 width, u32 height, NSView* view)
{
mg_gles_surface* surface = malloc_type(mg_gles_surface);
memset(surface, 0, sizeof(mg_gles_surface));
surface->interface.backend = MG_BACKEND_GLES;
surface->interface.destroy = mg_gles_surface_destroy;
surface->interface.prepare = mg_gles_surface_prepare;
surface->interface.present = mg_gles_surface_present;
surface->interface.resize = mg_gles_surface_resize;
surface->interface.getFrame = mg_gles_surface_get_frame;
surface->interface.setFrame = mg_gles_surface_set_frame;
surface->interface.getHidden = mg_gles_surface_get_hidden;
surface->interface.setHidden = mg_gles_surface_set_hidden;
surface->interface.getSize = mg_gles_surface_get_size;
surface->interface.getOSResource = mg_gles_surface_get_os_resource;
surface->view = view;
@autoreleasepool
{
surface->layer = [[CALayer alloc] init];
[surface->layer retain];
[surface->layer setBounds:CGRectMake(0, 0, width, height)];
if(surface->view)
{
[surface->view setWantsLayer: YES];
surface->view.layer = surface->layer;
}
}
EGLAttrib displayAttribs[] = {
EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE,
//NOTE: we need to explicitly set EGL_PLATFORM_ANGLE_TYPE_ANGLE to EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE, because
// EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE defaults to using CGL, and eglSetSwapInterval is broken for this backend
EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE,
EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE,
EGL_NONE};
@ -113,7 +141,7 @@ mg_surface mg_gles_surface_create_offscreen(u32 width, u32 height)
eglBindAPI(EGL_OPENGL_ES_API);
EGLint contextAttributes[] = {
EGL_CONTEXT_MAJOR_VERSION_KHR, 3,
EGL_CONTEXT_MINOR_VERSION_KHR, 0,
EGL_CONTEXT_MINOR_VERSION_KHR, 0, //NOTE: Angle can't create a GLES 3.1 context on macOS
EGL_CONTEXT_BIND_GENERATES_RESOURCE_CHROMIUM, EGL_TRUE,
EGL_CONTEXT_CLIENT_ARRAYS_ENABLED_ANGLE, EGL_TRUE,
EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE, EGL_FALSE,
@ -122,6 +150,35 @@ mg_surface mg_gles_surface_create_offscreen(u32 width, u32 height)
surface->eglContext = eglCreateContext(surface->eglDisplay, surface->eglConfig, EGL_NO_CONTEXT, contextAttributes);
eglMakeCurrent(surface->eglDisplay, surface->eglSurface, surface->eglSurface, surface->eglContext);
mg_surface handle = mg_surface_alloc_handle((mg_surface_info*)surface);
eglSwapInterval(surface->eglDisplay, 1);
mg_surface handle = mg_surface_alloc_handle((mg_surface_data*)surface);
return(handle);
}
mg_surface mg_gles_surface_create_offscreen(u32 width, u32 height)
{
return(mg_gles_surface_create_with_view(width, height, 0));
}
mg_surface mg_gles_surface_create_for_window(mp_window window)
{
mg_surface res = mg_surface_nil();
mp_window_data* windowData = mp_window_ptr_from_handle(window);
if(windowData)
{
@autoreleasepool
{
NSRect frame = [[windowData->osx.nsWindow contentView] frame];
NSView* view = [[NSView alloc] initWithFrame: frame];
res = mg_gles_surface_create_with_view(frame.size.width, frame.size.height, view);
if(!mg_surface_is_nil(res))
{
[[windowData->osx.nsWindow contentView] addSubview: view];
}
}
}
return(res);
}

View File

@ -207,6 +207,8 @@ mg_surface mg_gl_surface_create_for_window(mp_window window)
surface->interface.prepare = mg_gl_surface_prepare;
surface->interface.present = mg_gl_surface_present;
//TODO: get/set frame/hidden
surface->hDC = hDC;
surface->glContext = glContext;

View File

@ -1,4 +1,25 @@
[.] Check changes in macos version
[x] Restructure macos version to use mp_app_internal.h/mp_app.c
[x] test new run loop structure on macos
[x] Fix resize crash when there's no surface
[>] separate data for key and mouse event?
[>] Simplify event structs
[ ] use isARepeat in macos keyDown event and simplify update key state
[x] use surfaces to define restricted drawing locations
[x] Implement with NSView subviews on osx
[/] Maybe switch to just using CALayers?
[ ] Cleanup graphics backend compile-time/runtime selection
[ ] Cleanup graphics resource handles
[>>] OpenGL surface on OSX
[>>] Port vector graphics to OpenGL on OSX
[>] Check OpenGL vector graphics on win32
[ ] Implement surfaces with child windows on win32
[/] Maybe implement compositing directly in d3d and opengl compat extension...
Windows port
------------
[.] Finish events handling
@ -19,57 +40,6 @@ Windows port
(mp_app.c can 'see' platform specific stuff, so ObjectiveC defs pose a problem, but we can define id as void*
when not in ObjC...)
[.] Check changes in macos version
[x] Restructure macos version to use mp_app_internal.h/mp_app.c
[x] test new run loop structure on macos
[x] Fix resize crash when there's no surface
[>] separate data for key and mouse event?
[>] Simplify event structs
[ ] use isARepeat in macos keyDown event and simplify update key state
[>] Clarify how we want to do view handling across platforms
[.] use surfaces to define restricted drawing locations
[x] Implement with NSView subviews on osx
[/] Maybe switch to just using CALayers?
[ ] Clarify metal-specific surface API
> right now mg_surface doesn't really give access to directly drawing in metal
we only need render encoder from the surface? -> if we just want to issue draw calls.
We'll likely need the command buffer if we want other passes? or also have get_compute_encoder().
We only need the command buffer/command queue if we want to manually create encoders, eg with different clear color
-> get pixel format / command buffer / texture
(don't get drawable as it is specific to the kind of target (window or image))
-> as helper, get simple encoders in the command buffer
maybe even _just_ get a CAMetalDrawable and pixel format, and let user code create commandQueue, buffers, encoders...
-> but we need to present the drawable to a specific commandbuffer
-> so, maybe the view should also maintain a command buffer
-> pixel format, drawable, command buffer, + helpers for encoders(passes)
--> but then, mg_surface doesn't really abstract anything (usage code needs to know which kind of surface it is to be able to draw inside it)
(well, it abstracts the creation of the context and the presentation...)
maybe we should actually abstract over that, a la sokol_gfx... And then the 2D vector graphics uses this abstract API?
-> but this is a bigger endeavour
Maybe keep surface simple, write an opengl version, then try to port painter to opengl version,
and then we will have more info to decide if we want to abstract over the different APIs.
Do surfaces for different graphics API even need to be abstracted? don't we always know which surface we're using?
->> how much do we abstract graphics API vs how much we give access to native calls...
ie we could abstract shaders, buffers, textures, pipeline, encoders, etc... but also provide access to native data structures
[ ] Cleanup graphics backend compile-time/runtime selection
[ ] Implement surfaces with child windows on win32
[/] Maybe implement compositing directly in d3d and opengl compat extension...
[.] Implement input polling
[ ] Simplify input polling API names
[/] Try to simplify input state and polling once we have UI usage code
@ -88,9 +58,6 @@ Windows port
[ ] Remove unused APIs
[ ] OpenGL backend on OSX
[ ] Port vector graphics to OpenGL
[ ] Check OpenGL vector graphics on windows
Misc