initial commit
This commit is contained in:
commit
39cfa35bfd
|
@ -0,0 +1,3 @@
|
|||
.DS_Store
|
||||
bin/*
|
||||
*.metallib
|
|
@ -0,0 +1,81 @@
|
|||
#!/bin/bash
|
||||
|
||||
DEBUG_FLAGS="-g -DDEBUG -DLOG_COMPILE_DEBUG"
|
||||
#DEBUG_FLAGS="-O3"
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# set target
|
||||
#--------------------------------------------------------------
|
||||
|
||||
target="$1"
|
||||
if [ -z $target ] ; then
|
||||
target='lib'
|
||||
fi
|
||||
|
||||
shaderFlagParam="$2"
|
||||
#--------------------------------------------------------------
|
||||
# Detect OS and set environment variables accordingly
|
||||
#--------------------------------------------------------------
|
||||
OS=$(uname -s)
|
||||
|
||||
if [ $OS = "Darwin" ] ; then
|
||||
#echo "Target '$target' for macOS"
|
||||
CC=clang
|
||||
CXX=clang++
|
||||
DYLIB_SUFFIX='dylib'
|
||||
SYS_LIBS=''
|
||||
FLAGS="-mmacos-version-min=10.15.4 -DMG_IMPLEMENTS_BACKEND_METAL -maes"
|
||||
CFLAGS="-std=c11"
|
||||
|
||||
elif [ $OS = "Linux" ] ; then
|
||||
echo "Error: Linux is not supported yet"
|
||||
exit -1
|
||||
else
|
||||
echo "Error: Unsupported OS $OS"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# Set paths
|
||||
#--------------------------------------------------------------
|
||||
BINDIR="./bin"
|
||||
SRCDIR="./src"
|
||||
RESDIR="./resources"
|
||||
INCLUDES="-I$SRCDIR -I$SRCDIR/util -I$SRCDIR/platform"
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# Build
|
||||
#--------------------------------------------------------------
|
||||
|
||||
if [ ! \( -e bin \) ] ; then
|
||||
mkdir ./bin
|
||||
fi
|
||||
|
||||
if [ $target = 'lib' ] ; then
|
||||
|
||||
# compile metal shader
|
||||
xcrun -sdk macosx metal $shaderFlagParam -c -o $BINDIR/metal_shader.air $SRCDIR/metal_shader.metal
|
||||
xcrun -sdk macosx metallib -o $RESDIR/metal_shader.metallib $BINDIR/metal_shader.air
|
||||
|
||||
# compile milepost. We use one compilation unit for all C code, and one compilation
|
||||
# unit for all ObjectiveC code
|
||||
$CC $DEBUG_FLAGS -c -o $BINDIR/milepost_c.o $CFLAGS $FLAGS $INCLUDES $SRCDIR/milepost.c
|
||||
$CC $DEBUG_FLAGS -c -o $BINDIR/milepost_objc.o $FLAGS $INCLUDES $SRCDIR/milepost.mm
|
||||
|
||||
# build the static library
|
||||
libtool -static -o $BINDIR/libmilepost.a $BINDIR/milepost_c.o $BINDIR/milepost_objc.o
|
||||
|
||||
else
|
||||
# additional targets
|
||||
if [ $target = 'test' ] ; then
|
||||
pushd examples/test_app
|
||||
./build.sh
|
||||
popd
|
||||
|
||||
elif [ $target = 'clean' ] ; then
|
||||
rm -r ./bin
|
||||
else
|
||||
echo "unrecognized target $target"
|
||||
exit -1
|
||||
fi
|
||||
fi
|
|
@ -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 $BINDIR/test_app main.c
|
|
@ -0,0 +1,279 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: main.cpp
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 30/07/2022
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#include<stdlib.h>
|
||||
#include"milepost.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);
|
||||
|
||||
mg_init();
|
||||
ui_init();
|
||||
/*
|
||||
mp_rect frame = {0, 0, 800, 600};
|
||||
mp_view view = mp_view_create(window, frame);
|
||||
mg_surface surface = mg_surface_create_for_view(view, MG_BACKEND_METAL);
|
||||
/*/
|
||||
mg_surface surface = mg_surface_create_for_window(window, MG_BACKEND_METAL);
|
||||
//*/
|
||||
mg_canvas canvas = mg_canvas_create(surface, (mp_rect){0, 0, 800, 600});
|
||||
mg_image image = mg_image_create_from_file(canvas, str8_lit("Top512.png"), true);
|
||||
|
||||
u8 colors[64];
|
||||
for(int i=0; i<64; i+=4)
|
||||
{
|
||||
colors[i] = 255;
|
||||
colors[i+1] = 0;
|
||||
colors[i+2] = 0;
|
||||
colors[i+3] = 255;
|
||||
}
|
||||
mg_image image3 = mg_image_create_from_rgba8(canvas, 4, 4, colors);
|
||||
|
||||
mg_image image2 = mg_image_create_from_file(canvas, str8_lit("triceratops.png"), true);
|
||||
|
||||
//NOTE(martin): create font
|
||||
char* fontPath = 0;
|
||||
mp_app_get_resource_path("../resources/Andale Mono.ttf", &fontPath);
|
||||
FILE* fontFile = fopen(fontPath, "r");
|
||||
free(fontPath);
|
||||
if(!fontFile)
|
||||
{
|
||||
LOG_ERROR("Could not load font file '%s'\n", fontPath);
|
||||
return(-1);
|
||||
}
|
||||
unsigned char* fontData = 0;
|
||||
fseek(fontFile, 0, SEEK_END);
|
||||
u32 fontDataSize = ftell(fontFile);
|
||||
rewind(fontFile);
|
||||
fontData = (unsigned char*)malloc(fontDataSize);
|
||||
fread(fontData, 1, fontDataSize, fontFile);
|
||||
fclose(fontFile);
|
||||
|
||||
unicode_range ranges[5] = {UNICODE_RANGE_BASIC_LATIN,
|
||||
UNICODE_RANGE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT,
|
||||
UNICODE_RANGE_LATIN_EXTENDED_A,
|
||||
UNICODE_RANGE_LATIN_EXTENDED_B,
|
||||
UNICODE_RANGE_SPECIALS};
|
||||
|
||||
mg_font font = mg_font_create_from_memory(fontDataSize, fontData, 5, ranges);
|
||||
free(fontData);
|
||||
|
||||
mp_window_bring_to_front_and_focus(window);
|
||||
|
||||
mp_pump_events(-1);
|
||||
while(!mp_should_quit())
|
||||
{
|
||||
mp_event event = {};
|
||||
mp_pump_events(0);
|
||||
while(mp_next_event(&event))
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case MP_EVENT_KEYBOARD_CHAR:
|
||||
{
|
||||
printf("entered char %s\n", event.character.sequence);
|
||||
} break;
|
||||
|
||||
case MP_EVENT_WINDOW_CLOSE:
|
||||
{
|
||||
mp_do_quit();
|
||||
} break;
|
||||
|
||||
case MP_EVENT_WINDOW_RESIZE:
|
||||
{
|
||||
/*
|
||||
mp_rect frame = {0, 0, event.frame.rect.w, event.frame.rect.h};
|
||||
mp_view_set_frame(view, frame);
|
||||
*/
|
||||
mg_canvas_viewport(canvas, (mp_rect){0, 0, event.frame.rect.w, event.frame.rect.h});
|
||||
|
||||
} break;
|
||||
|
||||
case MP_EVENT_FRAME:
|
||||
{
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
mg_surface_prepare(surface);
|
||||
|
||||
mg_set_color_rgba(canvas, 1, 1, 1, 1);
|
||||
mg_clear(canvas);
|
||||
|
||||
mg_move_to(canvas, 0, 0);
|
||||
mg_line_to(canvas, 400, 300);
|
||||
mg_set_width(canvas, 5);
|
||||
mg_set_color_rgba(canvas, 1, 0, 0, 1);
|
||||
mg_stroke(canvas);
|
||||
|
||||
mg_image_draw(canvas, image, (mp_rect){400, 300, 200, 200});
|
||||
|
||||
mg_rounded_image_draw(canvas, image2, (mp_rect){200, 20, 200, 200}, 50);
|
||||
|
||||
mg_set_color_rgba(canvas, 0, 1, 0, 0.5);
|
||||
mg_rectangle_fill(canvas, 800, 800, 400, 200);
|
||||
|
||||
mg_set_font(canvas, font);
|
||||
mg_set_font_size(canvas, 32);
|
||||
mg_move_to(canvas, 500, 500);
|
||||
mg_text_outlines(canvas, str8_lit("Hello, world!"));
|
||||
mg_set_color_rgba(canvas, 0, 0, 0, 1);
|
||||
mg_fill(canvas);
|
||||
|
||||
mp_rect windowRect = mp_window_get_content_rect(window);
|
||||
|
||||
ui_style defaultStyle = {.borderSize = 2,
|
||||
.borderColor = {0, 0, 1, 1},
|
||||
.textColor = {0, 0, 0, 1},
|
||||
.font = font,
|
||||
.fontSize = 32};
|
||||
|
||||
ui_style buttonStyle[UI_STYLE_SELECTOR_COUNT];
|
||||
for(int i=0; i<UI_STYLE_SELECTOR_COUNT; i++)
|
||||
{
|
||||
buttonStyle[i] = (ui_style){ .backgroundColor = {0.8, 0.8, 0.8, 1},
|
||||
.foregroundColor = {0.5, 0.5, 0.5, 1},
|
||||
.borderSize = 2,
|
||||
.borderColor = {0, 0, 1, 1},
|
||||
.textColor = {0, 0, 0, 1},
|
||||
.font = font,
|
||||
.fontSize = 32 };
|
||||
}
|
||||
buttonStyle[UI_STYLE_HOT].borderColor = (mg_color){1, 0, 0, 1};
|
||||
buttonStyle[UI_STYLE_ACTIVE].borderColor = (mg_color){1, 0, 0, 1};
|
||||
buttonStyle[UI_STYLE_ACTIVE].foregroundColor = (mg_color){0.1, 0.1, 0.1, 1};
|
||||
|
||||
ui_begin_frame(windowRect.w, windowRect.h, defaultStyle);
|
||||
{
|
||||
ui_size_push(UI_AXIS_X, UI_SIZE_PIXELS, 600, 0);
|
||||
ui_size_push(UI_AXIS_Y, UI_SIZE_PIXELS, 200, 0);
|
||||
|
||||
for(int i=0; i<UI_STYLE_SELECTOR_COUNT; i++)
|
||||
{
|
||||
ui_style_push(i, buttonStyle[i]);
|
||||
}
|
||||
|
||||
ui_menu_bar("menu")
|
||||
{
|
||||
ui_menu("File")
|
||||
{
|
||||
ui_button("Open");
|
||||
ui_button("Save");
|
||||
ui_button("Save As");
|
||||
}
|
||||
|
||||
ui_menu("Edit")
|
||||
{
|
||||
ui_button("Cut");
|
||||
ui_button("Copy");
|
||||
ui_button("Paste");
|
||||
}
|
||||
|
||||
ui_menu("View")
|
||||
{
|
||||
ui_button("History");
|
||||
ui_button("Bookmarks");
|
||||
}
|
||||
}
|
||||
|
||||
ui_box* container = ui_box_begin("Test", UI_FLAG_DRAW_BORDER | UI_FLAG_DRAW_BACKGROUND);
|
||||
ui_box_set_layout(container, UI_AXIS_X, UI_ALIGN_END, UI_ALIGN_CENTER);
|
||||
{
|
||||
ui_size_push(UI_AXIS_X, UI_SIZE_TEXT, 0, 0);
|
||||
ui_size_push(UI_AXIS_Y, UI_SIZE_PARENT_RATIO, 0.25, 0);
|
||||
|
||||
ui_sig sig1 = ui_button("Child1");
|
||||
if(sig1.hovering)
|
||||
{
|
||||
ui_tooltip("tip")
|
||||
{
|
||||
ui_label("Click me!");
|
||||
}
|
||||
}
|
||||
if(sig1.triggered)
|
||||
{
|
||||
printf("clicked child 1!\n");
|
||||
}
|
||||
if(ui_button("Child2").triggered)
|
||||
{
|
||||
printf("clicked child 2!\n");
|
||||
}
|
||||
|
||||
ui_box* block = ui_box_make("block", UI_FLAG_DRAW_BACKGROUND | UI_FLAG_DRAW_BORDER | UI_FLAG_DRAW_TEXT | UI_FLAG_BLOCK_MOUSE);
|
||||
ui_box_set_size(block, UI_AXIS_X, UI_SIZE_TEXT, 0, 0);
|
||||
ui_box_set_size(block, UI_AXIS_Y, UI_SIZE_TEXT, 0, 0);
|
||||
ui_box_set_floating(block, UI_AXIS_X, 100);
|
||||
ui_box_set_floating(block, UI_AXIS_Y, 100);
|
||||
|
||||
ui_size_push(UI_AXIS_X, UI_SIZE_PIXELS, 200, 0);
|
||||
ui_size_push(UI_AXIS_Y, UI_SIZE_PIXELS, 20, 0);
|
||||
|
||||
static f32 scrollValue = 0.;
|
||||
ui_scrollbar("scroll", .25, &scrollValue);
|
||||
|
||||
ui_size_pop(UI_AXIS_X);
|
||||
ui_size_pop(UI_AXIS_Y);
|
||||
|
||||
ui_size_pop(UI_AXIS_X);
|
||||
ui_size_pop(UI_AXIS_Y);
|
||||
|
||||
} ui_box_end();
|
||||
|
||||
ui_size_pop(UI_AXIS_X);
|
||||
ui_size_pop(UI_AXIS_Y);
|
||||
|
||||
ui_size_push(UI_AXIS_X, UI_SIZE_PIXELS, 200, 0);
|
||||
ui_size_push(UI_AXIS_Y, UI_SIZE_PIXELS, 200, 0);
|
||||
ui_panel_begin("panel");
|
||||
|
||||
ui_size_push(UI_AXIS_X, UI_SIZE_TEXT, 0, 0);
|
||||
ui_size_push(UI_AXIS_Y, UI_SIZE_TEXT, 0, 0);
|
||||
|
||||
ui_box* b = ui_box_begin("container", 0);
|
||||
ui_box_set_size(b, UI_AXIS_X, UI_SIZE_CHILDREN, 0, 0);
|
||||
ui_box_set_size(b, UI_AXIS_Y, UI_SIZE_PIXELS, 300, 0);
|
||||
ui_button("Hello 1");
|
||||
ui_button("Hello 2");
|
||||
ui_button("Hello 3");
|
||||
ui_box_end();
|
||||
|
||||
ui_size_pop(UI_AXIS_X);
|
||||
ui_size_pop(UI_AXIS_Y);
|
||||
|
||||
ui_panel_end();
|
||||
ui_size_pop(UI_AXIS_X);
|
||||
ui_size_pop(UI_AXIS_Y);
|
||||
|
||||
for(int i=0; i<UI_STYLE_SELECTOR_COUNT; i++)
|
||||
{
|
||||
ui_style_pop(i);
|
||||
}
|
||||
|
||||
} ui_end_frame();
|
||||
|
||||
ui_draw(canvas);
|
||||
|
||||
mg_canvas_flush(canvas);
|
||||
|
||||
mg_surface_present(surface);
|
||||
}
|
||||
|
||||
mg_surface_destroy(surface);
|
||||
mp_terminate();
|
||||
return(0);
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,228 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: graphics.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 01/08/2022
|
||||
* @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_id;
|
||||
|
||||
void mg_init();
|
||||
|
||||
mg_surface mg_surface_nil();
|
||||
mg_surface mg_surface_create_for_window(mp_window window, mg_backend_id backend);
|
||||
mg_surface mg_surface_create_for_view(mp_view view, mg_backend_id backend);
|
||||
void mg_surface_destroy(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);
|
||||
|
||||
vec2 mg_surface_size(mg_surface surface);
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
//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;
|
||||
|
||||
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
|
||||
{
|
||||
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): canvas lifetime and command streams
|
||||
//------------------------------------------------------------------------------------------
|
||||
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);
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
//NOTE(martin): fonts management
|
||||
//------------------------------------------------------------------------------------------
|
||||
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
|
||||
|
||||
int mg_font_get_extents(mg_font font, mg_font_extents* outExtents);
|
||||
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(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
|
||||
//------------------------------------------------------------------------------------------
|
||||
void mg_get_current_position(mg_canvas context, f32* x, f32* y);
|
||||
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_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
|
||||
//------------------------------------------------------------------------------------------
|
||||
typedef struct mg_image { u64 h; } mg_image;
|
||||
|
||||
mg_image mg_image_create_from_rgba8(mg_canvas canvas, u32 width, u32 height, u8* pixels);
|
||||
mg_image mg_image_create_from_data(mg_canvas canvas, str8 data, bool flip);
|
||||
mg_image mg_image_create_from_file(mg_canvas canvas, str8 path, bool flip);
|
||||
|
||||
void mg_image_drestroy(mg_canvas canvas, mg_image image);
|
||||
|
||||
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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
#endif //__GRAPHICS_H_
|
|
@ -0,0 +1,90 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: graphics_internal.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 01/08/2022
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __GRAPHICS_INTERNAL_H_
|
||||
#define __GRAPHICS_INTERNAL_H_
|
||||
|
||||
#include"graphics.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct mg_surface_info mg_surface_info;
|
||||
|
||||
typedef void (*mg_surface_prepare_proc)(mg_surface_info* surface);
|
||||
typedef void (*mg_surface_present_proc)(mg_surface_info* surface);
|
||||
typedef void (*mg_surface_resize_proc)(mg_surface_info* surface, u32 width, u32 height);
|
||||
typedef vec2 (*mg_surface_get_size_proc)(mg_surface_info* surface);
|
||||
typedef void (*mg_surface_destroy_proc)(mg_surface_info* surface);
|
||||
|
||||
typedef struct mg_surface_info
|
||||
{
|
||||
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_size_proc getSize;
|
||||
|
||||
} mg_surface_info;
|
||||
|
||||
mg_surface mg_surface_alloc_handle(mg_surface_info* surface);
|
||||
|
||||
typedef struct mgc_vertex_layout
|
||||
{
|
||||
u32 maxVertexCount;
|
||||
u32 maxIndexCount;
|
||||
|
||||
void* posBuffer;
|
||||
u32 posStride;
|
||||
|
||||
void* cubicBuffer;
|
||||
u32 cubicStride;
|
||||
|
||||
void* uvBuffer;
|
||||
u32 uvStride;
|
||||
|
||||
void* colorBuffer;
|
||||
u32 colorStride;
|
||||
|
||||
void* zIndexBuffer;
|
||||
u32 zIndexStride;
|
||||
|
||||
void* clipsBuffer;
|
||||
u32 clipsStride;
|
||||
|
||||
void* indexBuffer;
|
||||
u32 indexStride;
|
||||
|
||||
} mgc_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_painter
|
||||
{
|
||||
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_canvas_painter;
|
||||
|
||||
#define MG_ATLAS_SIZE 8192
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif //__GRAPHICS_INTERNAL_H_
|
|
@ -0,0 +1,419 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: graphics.mm
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 12/07/2020
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#import<Metal/Metal.h>
|
||||
#import<QuartzCore/CAMetalLayer.h>
|
||||
#include<simd/simd.h>
|
||||
|
||||
#include"graphics_internal.h"
|
||||
#include"macro_helpers.h"
|
||||
#include"osx_app.h"
|
||||
|
||||
#include"metal_shader.h"
|
||||
|
||||
#define LOG_SUBSYSTEM "Graphics"
|
||||
|
||||
static const int MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH = 4<<20;
|
||||
|
||||
typedef struct mg_metal_painter
|
||||
{
|
||||
mg_canvas_painter interface;
|
||||
|
||||
mg_metal_surface* surface;
|
||||
|
||||
// permanent metal resources
|
||||
id<MTLComputePipelineState> tilingPipeline;
|
||||
id<MTLComputePipelineState> sortingPipeline;
|
||||
id<MTLComputePipelineState> boxingPipeline;
|
||||
id<MTLComputePipelineState> computePipeline;
|
||||
id<MTLRenderPipelineState> renderPipeline;
|
||||
|
||||
mp_rect viewPort;
|
||||
|
||||
// textures and buffers
|
||||
id<MTLTexture> outTexture;
|
||||
id<MTLTexture> atlasTexture;
|
||||
id<MTLBuffer> vertexBuffer;
|
||||
id<MTLBuffer> indexBuffer;
|
||||
id<MTLBuffer> tileCounters;
|
||||
id<MTLBuffer> tilesArray;
|
||||
id<MTLBuffer> triangleArray;
|
||||
id<MTLBuffer> boxArray;
|
||||
|
||||
} mg_metal_painter;
|
||||
|
||||
void mg_metal_painter_draw_buffers(mg_canvas_painter* interface, u32 vertexCount, u32 indexCount, mg_color clearColor)
|
||||
{
|
||||
mg_metal_painter* painter = (mg_metal_painter*)interface;
|
||||
mg_metal_surface* surface = painter->surface;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
if(surface->commandBuffer == nil || surface->commandBuffer == nil)
|
||||
{
|
||||
mg_metal_surface_acquire_drawable_and_command_buffer(surface);
|
||||
}
|
||||
|
||||
ASSERT(indexCount * sizeof(i32) < [painter->indexBuffer length]);
|
||||
|
||||
f32 scale = surface->metalLayer.contentsScale;
|
||||
vector_uint2 viewportSize = {painter->viewPort.w * scale, painter->viewPort.h * scale};
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): encode the clear counter
|
||||
//-----------------------------------------------------------
|
||||
id<MTLBlitCommandEncoder> blitEncoder = [surface->commandBuffer blitCommandEncoder];
|
||||
[blitEncoder fillBuffer: painter->tileCounters range: NSMakeRange(0, RENDERER_MAX_TILES*sizeof(uint)) value: 0];
|
||||
[blitEncoder endEncoding];
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): encode the boxing pass
|
||||
//-----------------------------------------------------------
|
||||
id<MTLComputeCommandEncoder> boxEncoder = [surface->commandBuffer computeCommandEncoder];
|
||||
[boxEncoder setComputePipelineState: painter->boxingPipeline];
|
||||
[boxEncoder setBuffer: painter->vertexBuffer offset:0 atIndex: 0];
|
||||
[boxEncoder setBuffer: painter->indexBuffer offset:0 atIndex: 1];
|
||||
[boxEncoder setBuffer: painter->triangleArray offset:0 atIndex: 2];
|
||||
[boxEncoder setBuffer: painter->boxArray offset:0 atIndex: 3];
|
||||
[boxEncoder setBytes: &scale length: sizeof(float) atIndex: 4];
|
||||
|
||||
MTLSize boxGroupSize = MTLSizeMake(painter->boxingPipeline.maxTotalThreadsPerThreadgroup, 1, 1);
|
||||
MTLSize boxGridSize = MTLSizeMake(indexCount/3, 1, 1);
|
||||
|
||||
[boxEncoder dispatchThreads: boxGridSize threadsPerThreadgroup: boxGroupSize];
|
||||
[boxEncoder endEncoding];
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): encode the tiling pass
|
||||
//-----------------------------------------------------------
|
||||
|
||||
id<MTLComputeCommandEncoder> tileEncoder = [surface->commandBuffer computeCommandEncoder];
|
||||
[tileEncoder setComputePipelineState: painter->tilingPipeline];
|
||||
[tileEncoder setBuffer: painter->boxArray offset:0 atIndex: 0];
|
||||
[tileEncoder setBuffer: painter->tileCounters offset:0 atIndex: 1];
|
||||
[tileEncoder setBuffer: painter->tilesArray offset:0 atIndex: 2];
|
||||
[tileEncoder setBytes: &viewportSize length: sizeof(vector_uint2) atIndex: 3];
|
||||
|
||||
[tileEncoder dispatchThreads: boxGridSize threadsPerThreadgroup: boxGroupSize];
|
||||
[tileEncoder endEncoding];
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): encode the sorting pass
|
||||
//-----------------------------------------------------------
|
||||
|
||||
id<MTLComputeCommandEncoder> sortEncoder = [surface->commandBuffer computeCommandEncoder];
|
||||
[sortEncoder setComputePipelineState: painter->sortingPipeline];
|
||||
[sortEncoder setBuffer: painter->tileCounters offset:0 atIndex: 0];
|
||||
[sortEncoder setBuffer: painter->triangleArray offset:0 atIndex: 1];
|
||||
[sortEncoder setBuffer: painter->tilesArray offset:0 atIndex: 2];
|
||||
[sortEncoder setBytes: &viewportSize length: sizeof(vector_uint2) atIndex: 3];
|
||||
|
||||
u32 nTilesX = (viewportSize.x + RENDERER_TILE_SIZE - 1)/RENDERER_TILE_SIZE;
|
||||
u32 nTilesY = (viewportSize.y + RENDERER_TILE_SIZE - 1)/RENDERER_TILE_SIZE;
|
||||
|
||||
MTLSize sortGroupSize = MTLSizeMake(painter->boxingPipeline.maxTotalThreadsPerThreadgroup, 1, 1);
|
||||
MTLSize sortGridSize = MTLSizeMake(nTilesX*nTilesY, 1, 1);
|
||||
|
||||
[sortEncoder dispatchThreads: sortGridSize threadsPerThreadgroup: sortGroupSize];
|
||||
[sortEncoder endEncoding];
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): create compute encoder and encode commands
|
||||
//-----------------------------------------------------------
|
||||
vector_float4 clearColorVec4 = {clearColor.r, clearColor.g, clearColor.b, clearColor.a};
|
||||
|
||||
id<MTLComputeCommandEncoder> encoder = [surface->commandBuffer computeCommandEncoder];
|
||||
[encoder setComputePipelineState:painter->computePipeline];
|
||||
[encoder setTexture: painter->outTexture atIndex: 0];
|
||||
[encoder setTexture: painter->atlasTexture atIndex: 1];
|
||||
[encoder setBuffer: painter->vertexBuffer offset:0 atIndex: 0];
|
||||
[encoder setBuffer: painter->tileCounters offset:0 atIndex: 1];
|
||||
[encoder setBuffer: painter->tilesArray offset:0 atIndex: 2];
|
||||
[encoder setBuffer: painter->triangleArray offset:0 atIndex: 3];
|
||||
[encoder setBuffer: painter->boxArray offset:0 atIndex: 4];
|
||||
[encoder setBytes: &clearColorVec4 length: sizeof(vector_float4) atIndex: 5];
|
||||
|
||||
//TODO: check that we don't exceed maxTotalThreadsPerThreadgroup
|
||||
DEBUG_ASSERT(RENDERER_TILE_SIZE*RENDERER_TILE_SIZE <= painter->computePipeline.maxTotalThreadsPerThreadgroup);
|
||||
MTLSize threadGridSize = MTLSizeMake(viewportSize.x, viewportSize.y, 1);
|
||||
MTLSize threadGroupSize = MTLSizeMake(RENDERER_TILE_SIZE, RENDERER_TILE_SIZE, 1);
|
||||
|
||||
[encoder dispatchThreads: threadGridSize threadsPerThreadgroup:threadGroupSize];
|
||||
[encoder endEncoding];
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): acquire drawable, create render encoder to blit texture to framebuffer
|
||||
//-----------------------------------------------------------
|
||||
|
||||
MTLViewport viewport = {painter->viewPort.x * scale,
|
||||
painter->viewPort.y * scale,
|
||||
painter->viewPort.w * scale,
|
||||
painter->viewPort.h * scale,
|
||||
0,
|
||||
1};
|
||||
|
||||
MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||
renderPassDescriptor.colorAttachments[0].texture = surface->drawable.texture;
|
||||
renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
|
||||
|
||||
id<MTLRenderCommandEncoder> renderEncoder = [surface->commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
|
||||
[renderEncoder setViewport: viewport];
|
||||
[renderEncoder setRenderPipelineState: painter->renderPipeline];
|
||||
[renderEncoder setFragmentTexture: painter->outTexture atIndex: 0];
|
||||
[renderEncoder drawPrimitives: MTLPrimitiveTypeTriangle
|
||||
vertexStart: 0
|
||||
vertexCount: 3 ];
|
||||
[renderEncoder endEncoding];
|
||||
}
|
||||
}
|
||||
|
||||
void mg_metal_painter_viewport(mg_canvas_painter* interface, mp_rect viewPort)
|
||||
{
|
||||
mg_metal_painter* painter = (mg_metal_painter*)interface;
|
||||
mg_metal_surface* surface = painter->surface;
|
||||
|
||||
painter->viewPort = viewPort;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
f32 scale = surface->metalLayer.contentsScale;
|
||||
CGSize drawableSize = (CGSize){.width = viewPort.w * scale, .height = viewPort.h * scale};
|
||||
|
||||
[painter->outTexture release];
|
||||
|
||||
MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init];
|
||||
texDesc.textureType = MTLTextureType2D;
|
||||
texDesc.storageMode = MTLStorageModePrivate;
|
||||
texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
|
||||
texDesc.pixelFormat = MTLPixelFormatBGRA8Unorm;// MTLPixelFormatBGRA8Unorm_sRGB;
|
||||
texDesc.width = drawableSize.width;
|
||||
texDesc.height = drawableSize.height;
|
||||
|
||||
painter->outTexture = [surface->device newTextureWithDescriptor:texDesc];
|
||||
}
|
||||
}
|
||||
|
||||
void mg_metal_painter_update_vertex_layout(mg_metal_painter* painter)
|
||||
{
|
||||
mg_metal_surface* surface = painter->surface;
|
||||
|
||||
char* vertexBase = (char*)[painter->vertexBuffer contents];
|
||||
|
||||
painter->interface.vertexLayout = (mgc_vertex_layout){
|
||||
.maxVertexCount = MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH,
|
||||
.maxIndexCount = MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH,
|
||||
.posBuffer = vertexBase + offsetof(mg_vertex, pos),
|
||||
.posStride = sizeof(mg_vertex),
|
||||
.cubicBuffer = vertexBase + offsetof(mg_vertex, cubic),
|
||||
.cubicStride = sizeof(mg_vertex),
|
||||
.uvBuffer = vertexBase + offsetof(mg_vertex, uv),
|
||||
.uvStride = sizeof(mg_vertex),
|
||||
.colorBuffer = vertexBase + offsetof(mg_vertex, color),
|
||||
.colorStride = sizeof(mg_vertex),
|
||||
.zIndexBuffer = vertexBase + offsetof(mg_vertex, zIndex),
|
||||
.zIndexStride = sizeof(mg_vertex),
|
||||
.clipsBuffer = vertexBase + offsetof(mg_vertex, clip),
|
||||
.clipsStride = sizeof(mg_vertex),
|
||||
.indexBuffer = [painter->indexBuffer contents],
|
||||
.indexStride = sizeof(int)};
|
||||
}
|
||||
|
||||
|
||||
void mg_metal_painter_destroy(mg_canvas_painter* interface)
|
||||
{
|
||||
mg_metal_painter* painter = (mg_metal_painter*)interface;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
[painter->outTexture release];
|
||||
[painter->atlasTexture release];
|
||||
[painter->vertexBuffer release];
|
||||
[painter->indexBuffer release];
|
||||
[painter->tilesArray release];
|
||||
[painter->triangleArray release];
|
||||
[painter->boxArray release];
|
||||
[painter->computePipeline release];
|
||||
}
|
||||
}
|
||||
|
||||
void mg_metal_painter_atlas_upload(mg_canvas_painter* interface, mp_rect rect, u8* bytes)
|
||||
{@autoreleasepool{
|
||||
mg_metal_painter* painter = (mg_metal_painter*)interface;
|
||||
|
||||
MTLRegion region = MTLRegionMake2D(rect.x, rect.y, rect.w, rect.h);
|
||||
[painter->atlasTexture replaceRegion:region
|
||||
mipmapLevel:0
|
||||
withBytes:(void*)bytes
|
||||
bytesPerRow: 4 * rect.w];
|
||||
}}
|
||||
|
||||
extern "C" mg_canvas_painter* mg_metal_painter_create_for_surface(mg_metal_surface* surface, mp_rect viewPort)
|
||||
{
|
||||
mg_metal_painter* painter = malloc_type(mg_metal_painter);
|
||||
painter->surface = surface;
|
||||
|
||||
//NOTE(martin): setup interface functions
|
||||
painter->interface.drawBuffers = mg_metal_painter_draw_buffers;
|
||||
painter->interface.setViewPort = mg_metal_painter_viewport;
|
||||
painter->interface.destroy = mg_metal_painter_destroy;
|
||||
painter->interface.atlasUpload = mg_metal_painter_atlas_upload;
|
||||
|
||||
painter->viewPort = viewPort;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
f32 scale = surface->metalLayer.contentsScale;
|
||||
CGSize drawableSize = (CGSize){.width = viewPort.w * scale, .height = viewPort.h * scale};
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): create our output texture
|
||||
//-----------------------------------------------------------
|
||||
MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init];
|
||||
texDesc.textureType = MTLTextureType2D;
|
||||
texDesc.storageMode = MTLStorageModePrivate;
|
||||
texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
|
||||
texDesc.pixelFormat = MTLPixelFormatBGRA8Unorm;// MTLPixelFormatBGRA8Unorm_sRGB;
|
||||
texDesc.width = drawableSize.width;
|
||||
texDesc.height = drawableSize.height;
|
||||
|
||||
painter->outTexture = [surface->device newTextureWithDescriptor:texDesc];
|
||||
//TODO(martin): retain ?
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): create our atlas texture
|
||||
//-----------------------------------------------------------
|
||||
texDesc.textureType = MTLTextureType2D;
|
||||
texDesc.storageMode = MTLStorageModeManaged;
|
||||
texDesc.usage = MTLTextureUsageShaderRead;
|
||||
texDesc.pixelFormat = MTLPixelFormatRGBA8Unorm; //MTLPixelFormatBGRA8Unorm;
|
||||
texDesc.width = MG_ATLAS_SIZE;
|
||||
texDesc.height = MG_ATLAS_SIZE;
|
||||
|
||||
painter->atlasTexture = [surface->device newTextureWithDescriptor:texDesc];
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): create buffers for vertex and index
|
||||
//-----------------------------------------------------------
|
||||
|
||||
MTLResourceOptions bufferOptions = MTLResourceCPUCacheModeWriteCombined
|
||||
| MTLResourceStorageModeShared;
|
||||
|
||||
painter->indexBuffer = [surface->device newBufferWithLength: MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH*sizeof(int)
|
||||
options: bufferOptions];
|
||||
|
||||
painter->vertexBuffer = [surface->device newBufferWithLength: MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH*sizeof(mg_vertex)
|
||||
options: bufferOptions];
|
||||
|
||||
painter->tilesArray = [surface->device newBufferWithLength: RENDERER_TILE_BUFFER_SIZE*sizeof(int)*RENDERER_MAX_TILES
|
||||
options: MTLResourceStorageModePrivate];
|
||||
|
||||
painter->triangleArray = [surface->device newBufferWithLength: MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH*sizeof(mg_triangle_data)
|
||||
options: MTLResourceStorageModePrivate];
|
||||
|
||||
painter->boxArray = [surface->device newBufferWithLength: MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH*sizeof(vector_float4)
|
||||
options: MTLResourceStorageModePrivate];
|
||||
|
||||
//TODO(martin): retain ?
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): create and initialize tile counters
|
||||
//-----------------------------------------------------------
|
||||
painter->tileCounters = [surface->device newBufferWithLength: RENDERER_MAX_TILES*sizeof(uint)
|
||||
options: MTLResourceStorageModePrivate];
|
||||
id<MTLCommandBuffer> commandBuffer = [surface->commandQueue commandBuffer];
|
||||
id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];
|
||||
[blitEncoder fillBuffer: painter->tileCounters range: NSMakeRange(0, RENDERER_MAX_TILES*sizeof(uint)) value: 0];
|
||||
[blitEncoder endEncoding];
|
||||
[commandBuffer commit];
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): load the library
|
||||
//-----------------------------------------------------------
|
||||
|
||||
//TODO(martin): filepath magic to find metallib path when not in the working directory
|
||||
char* shaderPath = 0;
|
||||
mp_app_get_resource_path("../resources/metal_shader.metallib", &shaderPath);
|
||||
NSString* metalFileName = [[NSString alloc] initWithCString: shaderPath encoding: NSUTF8StringEncoding];
|
||||
free(shaderPath);
|
||||
NSError* err = 0;
|
||||
id<MTLLibrary> library = [surface->device newLibraryWithFile: metalFileName error:&err];
|
||||
if(err != nil)
|
||||
{
|
||||
const char* errStr = [[err localizedDescription] UTF8String];
|
||||
LOG_ERROR("error : %s\n", errStr);
|
||||
return(0);
|
||||
}
|
||||
id<MTLFunction> tilingFunction = [library newFunctionWithName:@"TileKernel"];
|
||||
id<MTLFunction> sortingFunction = [library newFunctionWithName:@"SortKernel"];
|
||||
id<MTLFunction> boxingFunction = [library newFunctionWithName:@"BoundingBoxKernel"];
|
||||
id<MTLFunction> computeFunction = [library newFunctionWithName:@"RenderKernel"];
|
||||
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"VertexShader"];
|
||||
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"FragmentShader"];
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): setup our data layout and pipeline state
|
||||
//-----------------------------------------------------------
|
||||
NSError* error = NULL;
|
||||
painter->computePipeline = [surface->device newComputePipelineStateWithFunction: computeFunction
|
||||
error:&error];
|
||||
ASSERT(painter->computePipeline);
|
||||
|
||||
MTLComputePipelineDescriptor* tilingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init];
|
||||
tilingPipelineDesc.computeFunction = tilingFunction;
|
||||
// tilingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
|
||||
|
||||
painter->tilingPipeline = [surface->device newComputePipelineStateWithDescriptor: tilingPipelineDesc
|
||||
options: MTLPipelineOptionNone
|
||||
reflection: nil
|
||||
error: &error];
|
||||
|
||||
MTLComputePipelineDescriptor* sortingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init];
|
||||
sortingPipelineDesc.computeFunction = sortingFunction;
|
||||
// sortingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
|
||||
|
||||
painter->sortingPipeline = [surface->device newComputePipelineStateWithDescriptor: sortingPipelineDesc
|
||||
options: MTLPipelineOptionNone
|
||||
reflection: nil
|
||||
error: &error];
|
||||
|
||||
MTLComputePipelineDescriptor* boxingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init];
|
||||
boxingPipelineDesc.computeFunction = boxingFunction;
|
||||
// boxingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
|
||||
|
||||
painter->boxingPipeline = [surface->device newComputePipelineStateWithDescriptor: boxingPipelineDesc
|
||||
options: MTLPipelineOptionNone
|
||||
reflection: nil
|
||||
error: &error];
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): setup our render pipeline state
|
||||
//-----------------------------------------------------------
|
||||
// create and initialize the pipeline state descriptor
|
||||
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
|
||||
pipelineStateDescriptor.label = @"My simple pipeline";
|
||||
pipelineStateDescriptor.vertexFunction = vertexFunction;
|
||||
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
|
||||
pipelineStateDescriptor.colorAttachments[0].pixelFormat = surface->metalLayer.pixelFormat;
|
||||
|
||||
// create render pipeline
|
||||
painter->renderPipeline = [surface->device newRenderPipelineStateWithDescriptor: pipelineStateDescriptor error:&err];
|
||||
if(err != nil)
|
||||
{
|
||||
const char* errStr = [[err localizedDescription] UTF8String];
|
||||
const char* descStr = [[err localizedFailureReason] UTF8String];
|
||||
const char* recovStr = [[err localizedRecoverySuggestion] UTF8String];
|
||||
LOG_ERROR("(%li) %s. %s. %s\n", [err code], errStr, descStr, recovStr);
|
||||
return(0);
|
||||
}
|
||||
}
|
||||
|
||||
mg_metal_painter_update_vertex_layout(painter);
|
||||
|
||||
return((mg_canvas_painter*)painter);
|
||||
}
|
||||
|
||||
|
||||
#undef LOG_SUBSYSTEM
|
|
@ -0,0 +1,49 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: metal_shader.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 01/08/2022
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __METAL_RENDERER_H_
|
||||
#define __METAL_RENDERER_H_
|
||||
|
||||
#include<simd/simd.h>
|
||||
|
||||
#define RENDERER_TILE_BUFFER_SIZE 4096
|
||||
#define RENDERER_TILE_SIZE 16
|
||||
#define RENDERER_MAX_TILES 65536
|
||||
|
||||
#define RENDERER_DEBUG_TILE_VISITED 0xf00d
|
||||
#define RENDERER_DEBUG_TILE_BUFFER_OVERFLOW 0xdead
|
||||
|
||||
typedef struct mg_vertex
|
||||
{
|
||||
vector_float2 pos; // position
|
||||
vector_float4 cubic; // canonical implicit curve space coordinates
|
||||
vector_float2 uv; // texture coordinates
|
||||
vector_float4 color;
|
||||
vector_float4 clip;
|
||||
int zIndex;
|
||||
|
||||
} mg_vertex;
|
||||
|
||||
typedef struct mg_triangle_data
|
||||
{
|
||||
uint i0;
|
||||
uint i1;
|
||||
uint i2;
|
||||
uint zIndex;
|
||||
|
||||
vector_float2 p0;
|
||||
vector_float2 p1;
|
||||
vector_float2 p2;
|
||||
|
||||
int bias0;
|
||||
int bias1;
|
||||
int bias2;
|
||||
|
||||
} mg_triangle_data;
|
||||
|
||||
#endif //__METAL_RENDERER_H_
|
|
@ -0,0 +1,320 @@
|
|||
|
||||
#include<metal_stdlib>
|
||||
#include<simd/simd.h>
|
||||
|
||||
#include"metal_shader.h"
|
||||
|
||||
using namespace metal;
|
||||
|
||||
struct vs_out
|
||||
{
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
vertex vs_out VertexShader(ushort vid [[vertex_id]])
|
||||
{
|
||||
vs_out out;
|
||||
out.uv = float2((vid << 1) & 2, vid & 2);
|
||||
out.pos = float4(out.uv * float2(2, 2) + float2(-1, -1), 0, 1);
|
||||
return(out);
|
||||
}
|
||||
|
||||
fragment float4 FragmentShader(vs_out i [[stage_in]], texture2d<float> tex [[texture(0)]])
|
||||
{
|
||||
constexpr sampler smp(mip_filter::nearest, mag_filter::linear, min_filter::linear);
|
||||
return(float4(tex.sample(smp, i.uv).rgb, 1));
|
||||
}
|
||||
|
||||
|
||||
bool is_top_left(float2 a, float2 b)
|
||||
{
|
||||
return( (a.y == b.y && b.x < a.x)
|
||||
||(b.y < a.y));
|
||||
}
|
||||
|
||||
kernel void BoundingBoxKernel(constant mg_vertex* vertexBuffer [[buffer(0)]],
|
||||
constant uint* indexBuffer [[buffer(1)]],
|
||||
device mg_triangle_data* triangleArray [[buffer(2)]],
|
||||
device float4* boxArray [[buffer(3)]],
|
||||
constant float* contentsScaling [[buffer(4)]],
|
||||
uint gid [[thread_position_in_grid]])
|
||||
{
|
||||
uint triangleIndex = gid;
|
||||
uint vertexIndex = triangleIndex*3;
|
||||
|
||||
uint i0 = indexBuffer[vertexIndex];
|
||||
uint i1 = indexBuffer[vertexIndex+1];
|
||||
uint i2 = indexBuffer[vertexIndex+2];
|
||||
|
||||
float2 p0 = vertexBuffer[i0].pos * contentsScaling[0];
|
||||
float2 p1 = vertexBuffer[i1].pos * contentsScaling[0];
|
||||
float2 p2 = vertexBuffer[i2].pos * contentsScaling[0];
|
||||
|
||||
//NOTE(martin): compute triangle bounding box
|
||||
float2 boxMin = min(min(p0, p1), p2);
|
||||
float2 boxMax = max(max(p0, p1), p2);
|
||||
|
||||
//NOTE(martin): clip bounding box against clip rect
|
||||
vector_float4 clip = contentsScaling[0]*vertexBuffer[indexBuffer[vertexIndex]].clip;
|
||||
float2 clipMin(clip.x, clip.y);
|
||||
float2 clipMax(clip.x + clip.z-1, clip.y + clip.w-1);
|
||||
|
||||
//NOTE(martin): intersect with current clip
|
||||
boxMin = max(boxMin, clipMin);
|
||||
boxMax = min(boxMax, clipMax);
|
||||
|
||||
//NOTE(martin): reorder triangle counter-clockwise and compute bias for each edge
|
||||
float cw = (p1 - p0).x*(p2 - p0).y - (p1 - p0).y*(p2 - p0).x;
|
||||
if(cw < 0)
|
||||
{
|
||||
uint tmpIndex = i1;
|
||||
i1 = i2;
|
||||
i2 = tmpIndex;
|
||||
|
||||
float2 tmpPoint = p1;
|
||||
p1 = p2;
|
||||
p2 = tmpPoint;
|
||||
}
|
||||
int bias0 = is_top_left(p1, p2) ? 0 : -1;
|
||||
int bias1 = is_top_left(p2, p0) ? 0 : -1;
|
||||
int bias2 = is_top_left(p0, p1) ? 0 : -1;
|
||||
|
||||
//NOTE(martin): fill triangle data
|
||||
boxArray[triangleIndex] = float4(boxMin.x, boxMin.y, boxMax.x, boxMax.y);
|
||||
|
||||
triangleArray[triangleIndex].zIndex = vertexBuffer[i0].zIndex;
|
||||
triangleArray[triangleIndex].i0 = i0;
|
||||
triangleArray[triangleIndex].i1 = i1;
|
||||
triangleArray[triangleIndex].i2 = i2;
|
||||
triangleArray[triangleIndex].p0 = p0;
|
||||
triangleArray[triangleIndex].p1 = p1;
|
||||
triangleArray[triangleIndex].p2 = p2;
|
||||
triangleArray[triangleIndex].bias0 = bias0;
|
||||
triangleArray[triangleIndex].bias1 = bias1;
|
||||
triangleArray[triangleIndex].bias2 = bias2;
|
||||
}
|
||||
|
||||
kernel void TileKernel(const device float4* boxArray [[buffer(0)]],
|
||||
device volatile atomic_uint* tileCounters [[buffer(1)]],
|
||||
device uint* tilesArray [[buffer(2)]],
|
||||
constant vector_uint2* viewport [[buffer(3)]],
|
||||
uint gid [[thread_position_in_grid]])
|
||||
{
|
||||
uint2 tilesMatrixDim = (*viewport + RENDERER_TILE_SIZE - 1) / RENDERER_TILE_SIZE;
|
||||
uint nTilesX = tilesMatrixDim.x;
|
||||
uint nTilesY = tilesMatrixDim.y;
|
||||
|
||||
uint triangleIndex = gid;
|
||||
uint4 box = uint4(floor(boxArray[triangleIndex]/RENDERER_TILE_SIZE));
|
||||
uint xMin = max((uint)0, box.x);
|
||||
uint yMin = max((uint)0, box.y);
|
||||
uint xMax = min(box.z, nTilesX-1);
|
||||
uint yMax = min(box.w, nTilesY-1);
|
||||
|
||||
for(uint y = yMin; y <= yMax; y++)
|
||||
{
|
||||
for(uint x = xMin ; x <= xMax; x++)
|
||||
{
|
||||
uint tileIndex = y*nTilesX + x;
|
||||
device uint* tileBuffer = tilesArray + tileIndex*RENDERER_TILE_BUFFER_SIZE;
|
||||
uint counter = atomic_fetch_add_explicit(&(tileCounters[tileIndex]), 1, memory_order_relaxed);
|
||||
tileBuffer[counter] = triangleIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kernel void SortKernel(const device uint* tileCounters [[buffer(0)]],
|
||||
const device mg_triangle_data* triangleArray [[buffer(1)]],
|
||||
device uint* tilesArray [[buffer(2)]],
|
||||
constant vector_uint2* viewport [[buffer(3)]],
|
||||
uint gid [[thread_position_in_grid]])
|
||||
{
|
||||
uint tileIndex = gid;
|
||||
device uint* tileBuffer = tilesArray + tileIndex*RENDERER_TILE_BUFFER_SIZE;
|
||||
uint tileBufferSize = tileCounters[tileIndex];
|
||||
|
||||
for(int eltIndex=0; eltIndex < (int)tileBufferSize; eltIndex++)
|
||||
{
|
||||
uint elt = tileBuffer[eltIndex];
|
||||
uint eltZIndex = triangleArray[elt].zIndex;
|
||||
|
||||
int backIndex = eltIndex-1;
|
||||
for(; backIndex >= 0; backIndex--)
|
||||
{
|
||||
uint backElt = tileBuffer[backIndex];
|
||||
uint backEltZIndex = triangleArray[backElt].zIndex;
|
||||
if(eltZIndex >= backEltZIndex)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
tileBuffer[backIndex+1] = backElt;
|
||||
}
|
||||
}
|
||||
tileBuffer[backIndex+1] = elt;
|
||||
}
|
||||
}
|
||||
|
||||
float orient2d(float2 a, float2 b, float2 c)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
//TODO(martin): FIX this. This is a **horrible** quick hack to fix the precision issues
|
||||
// arising when a, b, and c are close. But it degrades when a, c, and c
|
||||
// are big. The proper solution is to change the expression to avoid
|
||||
// precision loss but I'm too busy/lazy to do it now.
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
a *= 10;
|
||||
b *= 10;
|
||||
c *= 10;
|
||||
return((b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x));
|
||||
}
|
||||
|
||||
kernel void RenderKernel(texture2d<float, access::write> outTexture [[texture(0)]],
|
||||
texture2d<float> texAtlas [[texture(1)]],
|
||||
const device mg_vertex* vertexBuffer [[buffer(0)]],
|
||||
device uint* tileCounters [[buffer(1)]],
|
||||
const device uint* tilesArray [[buffer(2)]],
|
||||
const device mg_triangle_data* triangleArray [[buffer(3)]],
|
||||
const device float4* boxArray [[buffer(4)]],
|
||||
constant vector_float4* clearColor [[buffer(5)]],
|
||||
uint2 gid [[thread_position_in_grid]],
|
||||
uint2 tgid [[threadgroup_position_in_grid]],
|
||||
uint2 threadsPerThreadgroup [[threads_per_threadgroup]],
|
||||
uint2 gridSize [[threads_per_grid]])
|
||||
{
|
||||
//TODO: guard against thread group size not equal to tile size?
|
||||
const uint2 tilesMatrixDim = (gridSize + RENDERER_TILE_SIZE - 1) / RENDERER_TILE_SIZE;
|
||||
const uint2 tilePos = tgid * threadsPerThreadgroup / RENDERER_TILE_SIZE;
|
||||
const uint tileIndex = tilePos.y * tilesMatrixDim.x + tilePos.x;
|
||||
const device uint* tileBuffer = tilesArray + tileIndex * RENDERER_TILE_BUFFER_SIZE;
|
||||
|
||||
const uint tileBufferSize = tileCounters[tileIndex];
|
||||
|
||||
#ifdef RENDERER_DEBUG_TILES
|
||||
//NOTE(martin): color code debug values and show the tile grid
|
||||
if((gid.x % RENDERER_TILE_SIZE == 0) || (gid.y % RENDERER_TILE_SIZE == 0))
|
||||
{
|
||||
outTexture.write(float4(0, 0, 0, 1), gid);
|
||||
return;
|
||||
}
|
||||
if(tileBufferSize <= 0)
|
||||
{
|
||||
outTexture.write(float4(0, 1, 0, 1), gid);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
const float2 sampleOffsets[6] = { float2(5./12, 5./12),
|
||||
float2(-3./12, 3./12),
|
||||
float2(1./12, 1./12),
|
||||
float2(3./12, -1./12),
|
||||
float2(-5./12, -3./12),
|
||||
float2(-1./12, -5./12)};
|
||||
|
||||
int zIndices[6];
|
||||
uint flipCounts[6];
|
||||
float4 pixelColors[6];
|
||||
float4 nextColors[6];
|
||||
for(int i=0; i<6; i++)
|
||||
{
|
||||
zIndices[i] = -1;
|
||||
flipCounts[i] = 0;
|
||||
pixelColors[i] = *clearColor;
|
||||
nextColors[i] = *clearColor;
|
||||
}
|
||||
|
||||
for(uint tileBufferIndex=0; tileBufferIndex < tileBufferSize; tileBufferIndex++)
|
||||
{
|
||||
float4 box = boxArray[tileBuffer[tileBufferIndex]];
|
||||
const device mg_triangle_data* triangle = &triangleArray[tileBuffer[tileBufferIndex]];
|
||||
|
||||
float2 p0 = triangle->p0;
|
||||
float2 p1 = triangle->p1;
|
||||
float2 p2 = triangle->p2;
|
||||
|
||||
int bias0 = triangle->bias0;
|
||||
int bias1 = triangle->bias1;
|
||||
int bias2 = triangle->bias2;
|
||||
|
||||
const device mg_vertex* v0 = &(vertexBuffer[triangle->i0]);
|
||||
const device mg_vertex* v1 = &(vertexBuffer[triangle->i1]);
|
||||
const device mg_vertex* v2 = &(vertexBuffer[triangle->i2]);
|
||||
|
||||
float4 cubic0 = v0->cubic;
|
||||
float4 cubic1 = v1->cubic;
|
||||
float4 cubic2 = v2->cubic;
|
||||
|
||||
float2 uv0 = v0->uv;
|
||||
float2 uv1 = v1->uv;
|
||||
float2 uv2 = v2->uv;
|
||||
|
||||
int zIndex = v0->zIndex;
|
||||
float4 color = v0->color;
|
||||
|
||||
for(int i=0; i<6; i++)
|
||||
{
|
||||
float2 samplePoint = (float2)gid + sampleOffsets[i];
|
||||
|
||||
//NOTE(martin): cull if pixel is outside box
|
||||
if(samplePoint.x < box.x || samplePoint.x > box.z || samplePoint.y < box.y || samplePoint.y > box.w)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float w0 = orient2d(p1, p2, samplePoint);
|
||||
float w1 = orient2d(p2, p0, samplePoint);
|
||||
float w2 = orient2d(p0, p1, samplePoint);
|
||||
|
||||
if(((int)w0+bias0) >= 0 && ((int)w1+bias1) >= 0 && ((int)w2+bias2) >= 0)
|
||||
{
|
||||
float4 cubic = (cubic0*w0 + cubic1*w1 + cubic2*w2)/(w0+w1+w2);
|
||||
float2 uv = (uv0*w0 + uv1*w1 + uv2*w2)/(w0+w1+w2);
|
||||
|
||||
constexpr sampler smp(mip_filter::nearest, mag_filter::linear, min_filter::linear);
|
||||
float4 texColor = texAtlas.sample(smp, uv);
|
||||
|
||||
//TODO(martin): this is a quick and dirty fix for solid polygons where we use
|
||||
// cubic = (1, 1, 1, 1) on all vertices, which can cause small errors to
|
||||
// flip the sign.
|
||||
// We should really use another value that always lead to <= 0, but we must
|
||||
// make sure we never share these vertices with bezier shapes.
|
||||
// Alternatively, an ugly (but maybe less than this one) solution would be
|
||||
// to check if uvs are equal on all vertices of the triangle and always render
|
||||
// those triangles.
|
||||
float eps = 0.0001;
|
||||
if(cubic.w*(cubic.x*cubic.x*cubic.x - cubic.y*cubic.z) <= eps)
|
||||
{
|
||||
if(zIndex == zIndices[i])
|
||||
{
|
||||
flipCounts[i]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(flipCounts[i] & 0x01)
|
||||
{
|
||||
pixelColors[i] = nextColors[i];
|
||||
}
|
||||
|
||||
float4 nextCol = color*texColor;
|
||||
nextColors[i] = pixelColors[i]*(1-nextCol.a) +nextCol.a*nextCol;
|
||||
|
||||
zIndices[i] = zIndex;
|
||||
flipCounts[i] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
float4 out = float4(0, 0, 0, 0);
|
||||
for(int i=0; i<6; i++)
|
||||
{
|
||||
if(flipCounts[i] & 0x01)
|
||||
{
|
||||
pixelColors[i] = nextColors[i];
|
||||
}
|
||||
out += pixelColors[i];
|
||||
}
|
||||
out = float4(out.xyz/6, 1);
|
||||
outTexture.write(out, gid);
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: graphics.mm
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 12/07/2020
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#import<Metal/Metal.h>
|
||||
#import<QuartzCore/CAMetalLayer.h>
|
||||
#include<simd/simd.h>
|
||||
|
||||
#include"graphics_internal.h"
|
||||
#include"macro_helpers.h"
|
||||
#include"osx_app.h"
|
||||
|
||||
#define LOG_SUBSYSTEM "Graphics"
|
||||
|
||||
static const u32 MP_METAL_MAX_DRAWABLES_IN_FLIGHT = 3;
|
||||
|
||||
typedef struct mg_metal_surface
|
||||
{
|
||||
mg_surface_info interface;
|
||||
|
||||
// permanent metal resources
|
||||
NSView* view;
|
||||
id<MTLDevice> device;
|
||||
CAMetalLayer* metalLayer;
|
||||
id<MTLCommandQueue> commandQueue;
|
||||
|
||||
// transient metal resources
|
||||
id<CAMetalDrawable> drawable;
|
||||
id<MTLCommandBuffer> commandBuffer;
|
||||
|
||||
dispatch_semaphore_t drawableSemaphore;
|
||||
|
||||
} mg_metal_surface;
|
||||
|
||||
void mg_metal_surface_acquire_drawable_and_command_buffer(mg_metal_surface* surface)
|
||||
{@autoreleasepool{
|
||||
/*WARN(martin): this is super important
|
||||
When the app is put in the background, it seems that if there are buffers in flight, the drawables to
|
||||
can be leaked. This causes the gpu to allocate more and more drawables, until the app crashes.
|
||||
(note: the drawable objects themselves are released once the app comes back to the forefront, but the
|
||||
memory allocated in the GPU is never freed...)
|
||||
|
||||
In background the gpu seems to create drawable if none is available instead of actually
|
||||
blocking on nextDrawable. These drawable never get freed.
|
||||
This is not a problem if our shader is fast enough, since a previous drawable becomes
|
||||
available before we finish the frame. But we want to protect against it anyway
|
||||
|
||||
The normal blocking mechanism of nextDrawable seems useless, so we implement our own scheme by
|
||||
counting the number of drawables available with a semaphore that gets decremented here and
|
||||
incremented in the presentedHandler of the drawable.
|
||||
Thus we ensure that we don't consume more drawables than we are able to draw.
|
||||
|
||||
//TODO: we _also_ should stop trying to render if we detect that the app is in the background
|
||||
or occluded, but we can't count only on this because there is a potential race between the
|
||||
notification of background mode and the rendering.
|
||||
|
||||
//TODO: We should set a reasonable timeout and skip the frame and log an error in case we are stalled
|
||||
for too long.
|
||||
*/
|
||||
dispatch_semaphore_wait(surface->drawableSemaphore, DISPATCH_TIME_FOREVER);
|
||||
|
||||
surface->drawable = [surface->metalLayer nextDrawable];
|
||||
ASSERT(surface->drawable != nil);
|
||||
|
||||
//TODO: make this a weak reference if we use ARC
|
||||
dispatch_semaphore_t semaphore = surface->drawableSemaphore;
|
||||
[surface->drawable addPresentedHandler:^(id<MTLDrawable> drawable){
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
|
||||
//NOTE(martin): create a command buffer
|
||||
surface->commandBuffer = [surface->commandQueue commandBuffer];
|
||||
|
||||
[surface->commandBuffer retain];
|
||||
[surface->drawable retain];
|
||||
}}
|
||||
|
||||
void mg_metal_surface_prepare(mg_surface_info* interface)
|
||||
{
|
||||
mg_metal_surface* surface = (mg_metal_surface*)interface;
|
||||
mg_metal_surface_acquire_drawable_and_command_buffer(surface);
|
||||
}
|
||||
|
||||
void mg_metal_surface_present(mg_surface_info* interface)
|
||||
{
|
||||
mg_metal_surface* surface = (mg_metal_surface*)interface;
|
||||
@autoreleasepool
|
||||
{
|
||||
//NOTE(martin): present drawable and commit command buffer
|
||||
[surface->commandBuffer presentDrawable: surface->drawable];
|
||||
[surface->commandBuffer commit];
|
||||
[surface->commandBuffer waitUntilCompleted];
|
||||
|
||||
//NOTE(martin): acquire next frame resources
|
||||
[surface->commandBuffer release];
|
||||
surface->commandBuffer = nil;
|
||||
[surface->drawable release];
|
||||
surface->drawable = nil;
|
||||
}
|
||||
}
|
||||
|
||||
void mg_metal_surface_destroy(mg_surface_info* interface)
|
||||
{
|
||||
mg_metal_surface* surface = (mg_metal_surface*)interface;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
[surface->commandQueue release];
|
||||
[surface->metalLayer release];
|
||||
[surface->device release];
|
||||
}
|
||||
}
|
||||
|
||||
void mg_metal_surface_resize(mg_surface_info* interface, u32 width, u32 height)
|
||||
{
|
||||
mg_metal_surface* surface = (mg_metal_surface*)interface;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
//TODO(martin): actually detect scaling
|
||||
CGRect frame = {{0, 0}, {width, height}};
|
||||
[surface->view setFrame: frame];
|
||||
f32 scale = surface->metalLayer.contentsScale;
|
||||
CGSize drawableSize = (CGSize){.width = width * scale, .height = height * scale};
|
||||
surface->metalLayer.drawableSize = drawableSize;
|
||||
}
|
||||
}
|
||||
|
||||
vec2 mg_metal_surface_get_size(mg_surface_info* interface)
|
||||
{
|
||||
mg_metal_surface* surface = (mg_metal_surface*)interface;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
//TODO(martin): actually detect scaling
|
||||
CGRect frame = surface->view.frame;
|
||||
return((vec2){frame.size.width, frame.size.height});
|
||||
}
|
||||
}
|
||||
|
||||
static const f32 MG_METAL_SURFACE_CONTENTS_SCALING = 2;
|
||||
|
||||
extern "C" mg_surface mg_metal_surface_create_for_view(mp_view view)
|
||||
{
|
||||
mp_view_data* viewData = mp_view_ptr_from_handle(view);
|
||||
if(!viewData)
|
||||
{
|
||||
return(mg_surface_nil());
|
||||
}
|
||||
else
|
||||
{
|
||||
mg_metal_surface* surface = (mg_metal_surface*)malloc(sizeof(mg_metal_surface));
|
||||
|
||||
//NOTE(martin): setup interface functions
|
||||
surface->interface.backend = MG_BACKEND_METAL;
|
||||
surface->interface.destroy = mg_metal_surface_destroy;
|
||||
surface->interface.prepare = mg_metal_surface_prepare;
|
||||
surface->interface.present = mg_metal_surface_present;
|
||||
surface->interface.resize = mg_metal_surface_resize;
|
||||
surface->interface.getSize = mg_metal_surface_get_size;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
surface->drawableSemaphore = dispatch_semaphore_create(MP_METAL_MAX_DRAWABLES_IN_FLIGHT);
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): create a metal device and a metal layer and
|
||||
//-----------------------------------------------------------
|
||||
surface->device = MTLCreateSystemDefaultDevice();
|
||||
[surface->device retain];
|
||||
surface->metalLayer = [CAMetalLayer layer];
|
||||
[surface->metalLayer retain];
|
||||
surface->metalLayer.device = surface->device;
|
||||
|
||||
surface->view = viewData->nsView;
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): set the size and scaling
|
||||
//-----------------------------------------------------------
|
||||
CGSize size = surface->view.bounds.size;
|
||||
size.width *= MG_METAL_SURFACE_CONTENTS_SCALING;
|
||||
size.height *= MG_METAL_SURFACE_CONTENTS_SCALING;
|
||||
surface->metalLayer.drawableSize = size;
|
||||
surface->metalLayer.contentsScale = MG_METAL_SURFACE_CONTENTS_SCALING;
|
||||
|
||||
surface->metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
|
||||
|
||||
surface->view.wantsLayer = YES;
|
||||
surface->view.layer = surface->metalLayer;
|
||||
|
||||
//NOTE(martin): handling resizing
|
||||
surface->view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
|
||||
surface->metalLayer.autoresizingMask = kCALayerHeightSizable | kCALayerWidthSizable;
|
||||
surface->metalLayer.needsDisplayOnBoundsChange = YES;
|
||||
|
||||
//-----------------------------------------------------------
|
||||
//NOTE(martin): create a command queue
|
||||
//-----------------------------------------------------------
|
||||
surface->commandQueue = [surface->device newCommandQueue];
|
||||
[surface->commandQueue retain];
|
||||
|
||||
//NOTE(martin): command buffer and drawable are set on demand and at the end of each present() call
|
||||
surface->drawable = nil;
|
||||
surface->commandBuffer = nil;
|
||||
}
|
||||
|
||||
mg_surface handle = mg_surface_alloc_handle((mg_surface_info*)surface);
|
||||
viewData->surface = handle;
|
||||
|
||||
return(handle);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" mg_surface mg_metal_surface_create_for_window(mp_window window)
|
||||
{
|
||||
mp_window_data* windowData = mp_window_ptr_from_handle(window);
|
||||
if(!windowData)
|
||||
{
|
||||
return(mg_surface_nil());
|
||||
}
|
||||
else
|
||||
{
|
||||
return(mg_metal_surface_create_for_view(windowData->mainView));
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOG_SUBSYSTEM
|
|
@ -0,0 +1,29 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: milepost.c
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 13/02/2021
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
|
||||
#include"util/debug_log.c"
|
||||
#include"memory.c"
|
||||
#include"strings.c"
|
||||
#include"ringbuffer.c"
|
||||
#include"util/utf8.c"
|
||||
#include"util/hash.c"
|
||||
|
||||
#include"platform/unix_base_allocator.c"
|
||||
|
||||
#include"graphics.c"
|
||||
#include"ui.c"
|
||||
|
||||
//TODO: guard these under platform-specific #ifdefs
|
||||
#include"platform/osx_clock.c"
|
||||
/*
|
||||
|
||||
#include"platform/unix_rng.c"
|
||||
#include"platform/posix_thread.c"
|
||||
#include"platform/posix_socket.c"
|
||||
*/
|
|
@ -0,0 +1,31 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: milepost.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 13/02/2021
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __MILEPOST_H_
|
||||
#define __MILEPOST_H_
|
||||
|
||||
|
||||
#include"typedefs.h"
|
||||
#include"macro_helpers.h"
|
||||
#include"debug_log.h"
|
||||
#include"lists.h"
|
||||
#include"memory.h"
|
||||
#include"strings.h"
|
||||
#include"utf8.h"
|
||||
|
||||
#include"platform_clock.h"
|
||||
/*
|
||||
#include"platform_rng.h"
|
||||
#include"platform_socket.h"
|
||||
#include"platform_thread.h"
|
||||
*/
|
||||
#include"mp_app.h"
|
||||
#include"graphics.h"
|
||||
#include"ui.h"
|
||||
|
||||
#endif //__MILEPOST_H_
|
|
@ -0,0 +1,12 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: milepost.mm
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 13/02/2021
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
|
||||
#include"osx_app.mm"
|
||||
#include"metal_surface.mm"
|
||||
#include"metal_painter.mm"
|
|
@ -0,0 +1,413 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: platform_app.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 16/05/2020
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __PLATFORM_APP_H_
|
||||
#define __PLATFORM_APP_H_
|
||||
|
||||
#include"typedefs.h"
|
||||
#include"utf8.h"
|
||||
#include"lists.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Typedefs, enums and constants
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
typedef struct mp_window { u64 h; } mp_window;
|
||||
typedef struct mp_view { u64 h; } mp_view;
|
||||
|
||||
typedef enum { MP_MOUSE_CURSOR_ARROW,
|
||||
MP_MOUSE_CURSOR_RESIZE_0,
|
||||
MP_MOUSE_CURSOR_RESIZE_90,
|
||||
MP_MOUSE_CURSOR_RESIZE_45,
|
||||
MP_MOUSE_CURSOR_RESIZE_135,
|
||||
MP_MOUSE_CURSOR_TEXT } mp_mouse_cursor;
|
||||
|
||||
typedef i32 mp_window_style;
|
||||
static const mp_window_style MP_WINDOW_STYLE_NO_TITLE = 0x01<<0,
|
||||
MP_WINDOW_STYLE_FIXED_SIZE = 0x01<<1,
|
||||
MP_WINDOW_STYLE_NO_CLOSE = 0x01<<2,
|
||||
MP_WINDOW_STYLE_NO_MINIFY = 0x01<<3,
|
||||
MP_WINDOW_STYLE_NO_FOCUS = 0x01<<4,
|
||||
MP_WINDOW_STYLE_FLOAT = 0x01<<5,
|
||||
MP_WINDOW_STYLE_POPUPMENU = 0x01<<6,
|
||||
MP_WINDOW_STYLE_NO_BUTTONS = 0x01<<7;
|
||||
|
||||
typedef enum { MP_EVENT_NONE,
|
||||
MP_EVENT_KEYBOARD_MODS,
|
||||
MP_EVENT_KEYBOARD_KEY,
|
||||
MP_EVENT_KEYBOARD_CHAR,
|
||||
MP_EVENT_MOUSE_BUTTON,
|
||||
MP_EVENT_MOUSE_MOVE,
|
||||
MP_EVENT_MOUSE_WHEEL,
|
||||
MP_EVENT_MOUSE_ENTER,
|
||||
MP_EVENT_MOUSE_LEAVE,
|
||||
MP_EVENT_WINDOW_RESIZE,
|
||||
MP_EVENT_WINDOW_MOVE,
|
||||
MP_EVENT_WINDOW_FOCUS,
|
||||
MP_EVENT_WINDOW_UNFOCUS,
|
||||
MP_EVENT_WINDOW_HIDE,
|
||||
MP_EVENT_WINDOW_SHOW,
|
||||
MP_EVENT_WINDOW_CLOSE,
|
||||
MP_EVENT_CLIPBOARD,
|
||||
MP_EVENT_PATHDROP,
|
||||
MP_EVENT_FRAME,
|
||||
MP_EVENT_QUIT } mp_event_type;
|
||||
|
||||
typedef enum { MP_KEY_NO_ACTION,
|
||||
MP_KEY_PRESS,
|
||||
MP_KEY_RELEASE,
|
||||
MP_KEY_REPEAT } mp_key_action;
|
||||
|
||||
typedef i32 mp_key_code;
|
||||
|
||||
static const mp_key_code MP_KEY_UNKNOWN = -1,
|
||||
MP_KEY_SPACE = 32,
|
||||
MP_KEY_APOSTROPHE = 39, // '
|
||||
MP_KEY_COMMA = 44, // ,
|
||||
MP_KEY_MINUS = 45, // -
|
||||
MP_KEY_PERIOD = 46, // .
|
||||
MP_KEY_SLASH = 47, // /
|
||||
MP_KEY_0 = 48,
|
||||
MP_KEY_1 = 49,
|
||||
MP_KEY_2 = 50,
|
||||
MP_KEY_3 = 51,
|
||||
MP_KEY_4 = 52,
|
||||
MP_KEY_5 = 53,
|
||||
MP_KEY_6 = 54,
|
||||
MP_KEY_7 = 55,
|
||||
MP_KEY_8 = 56,
|
||||
MP_KEY_9 = 57,
|
||||
MP_KEY_SEMICOLON = 59, // ;
|
||||
MP_KEY_EQUAL = 61, // =
|
||||
MP_KEY_A = 65,
|
||||
MP_KEY_B = 66,
|
||||
MP_KEY_C = 67,
|
||||
MP_KEY_D = 68,
|
||||
MP_KEY_E = 69,
|
||||
MP_KEY_F = 70,
|
||||
MP_KEY_G = 71,
|
||||
MP_KEY_H = 72,
|
||||
MP_KEY_I = 73,
|
||||
MP_KEY_J = 74,
|
||||
MP_KEY_K = 75,
|
||||
MP_KEY_L = 76,
|
||||
MP_KEY_M = 77,
|
||||
MP_KEY_N = 78,
|
||||
MP_KEY_O = 79,
|
||||
MP_KEY_P = 80,
|
||||
MP_KEY_Q = 81,
|
||||
MP_KEY_R = 82,
|
||||
MP_KEY_S = 83,
|
||||
MP_KEY_T = 84,
|
||||
MP_KEY_U = 85,
|
||||
MP_KEY_V = 86,
|
||||
MP_KEY_W = 87,
|
||||
MP_KEY_X = 88,
|
||||
MP_KEY_Y = 89,
|
||||
MP_KEY_Z = 90,
|
||||
MP_KEY_LEFT_BRACKET = 91, // [
|
||||
MP_KEY_BACKSLASH = 92, // \ */
|
||||
MP_KEY_RIGHT_BRACKET = 93, // ]
|
||||
MP_KEY_GRAVE_ACCENT = 96, // `
|
||||
MP_KEY_WORLD_1 = 161, // non-US #1
|
||||
MP_KEY_WORLD_2 = 162, // non-US #2
|
||||
MP_KEY_ESCAPE = 256,
|
||||
MP_KEY_ENTER = 257,
|
||||
MP_KEY_TAB = 258,
|
||||
MP_KEY_BACKSPACE = 259,
|
||||
MP_KEY_INSERT = 260,
|
||||
MP_KEY_DELETE = 261,
|
||||
MP_KEY_RIGHT = 262,
|
||||
MP_KEY_LEFT = 263,
|
||||
MP_KEY_DOWN = 264,
|
||||
MP_KEY_UP = 265,
|
||||
MP_KEY_PAGE_UP = 266,
|
||||
MP_KEY_PAGE_DOWN = 267,
|
||||
MP_KEY_HOME = 268,
|
||||
MP_KEY_END = 269,
|
||||
MP_KEY_CAPS_LOCK = 280,
|
||||
MP_KEY_SCROLL_LOCK = 281,
|
||||
MP_KEY_NUM_LOCK = 282,
|
||||
MP_KEY_PRINT_SCREEN = 283,
|
||||
MP_KEY_PAUSE = 284,
|
||||
MP_KEY_F1 = 290,
|
||||
MP_KEY_F2 = 291,
|
||||
MP_KEY_F3 = 292,
|
||||
MP_KEY_F4 = 293,
|
||||
MP_KEY_F5 = 294,
|
||||
MP_KEY_F6 = 295,
|
||||
MP_KEY_F7 = 296,
|
||||
MP_KEY_F8 = 297,
|
||||
MP_KEY_F9 = 298,
|
||||
MP_KEY_F10 = 299,
|
||||
MP_KEY_F11 = 300,
|
||||
MP_KEY_F12 = 301,
|
||||
MP_KEY_F13 = 302,
|
||||
MP_KEY_F14 = 303,
|
||||
MP_KEY_F15 = 304,
|
||||
MP_KEY_F16 = 305,
|
||||
MP_KEY_F17 = 306,
|
||||
MP_KEY_F18 = 307,
|
||||
MP_KEY_F19 = 308,
|
||||
MP_KEY_F20 = 309,
|
||||
MP_KEY_F21 = 310,
|
||||
MP_KEY_F22 = 311,
|
||||
MP_KEY_F23 = 312,
|
||||
MP_KEY_F24 = 313,
|
||||
MP_KEY_F25 = 314,
|
||||
MP_KEY_KP_0 = 320,
|
||||
MP_KEY_KP_1 = 321,
|
||||
MP_KEY_KP_2 = 322,
|
||||
MP_KEY_KP_3 = 323,
|
||||
MP_KEY_KP_4 = 324,
|
||||
MP_KEY_KP_5 = 325,
|
||||
MP_KEY_KP_6 = 326,
|
||||
MP_KEY_KP_7 = 327,
|
||||
MP_KEY_KP_8 = 328,
|
||||
MP_KEY_KP_9 = 329,
|
||||
MP_KEY_KP_DECIMAL = 330,
|
||||
MP_KEY_KP_DIVIDE = 331,
|
||||
MP_KEY_KP_MULTIPLY = 332,
|
||||
MP_KEY_KP_SUBTRACT = 333,
|
||||
MP_KEY_KP_ADD = 334,
|
||||
MP_KEY_KP_ENTER = 335,
|
||||
MP_KEY_KP_EQUAL = 336,
|
||||
MP_KEY_LEFT_SHIFT = 340,
|
||||
MP_KEY_LEFT_CONTROL = 341,
|
||||
MP_KEY_LEFT_ALT = 342,
|
||||
MP_KEY_LEFT_SUPER = 343,
|
||||
MP_KEY_RIGHT_SHIFT = 344,
|
||||
MP_KEY_RIGHT_CONTROL = 345,
|
||||
MP_KEY_RIGHT_ALT = 346,
|
||||
MP_KEY_RIGHT_SUPER = 347,
|
||||
MP_KEY_MENU = 348;
|
||||
|
||||
static const mp_key_code MP_KEY_MAX = MP_KEY_MENU;
|
||||
|
||||
typedef u8 mp_key_mods;
|
||||
static const mp_key_mods MP_KEYMOD_NONE = 0x00,
|
||||
MP_KEYMOD_ALT = 0x01,
|
||||
MP_KEYMOD_SHIFT = 0x02,
|
||||
MP_KEYMOD_CTRL = 0x04,
|
||||
MP_KEYMOD_CMD = 0x08;
|
||||
|
||||
typedef i32 mp_mouse_button;
|
||||
static const mp_mouse_button MP_MOUSE_LEFT = 0x00,
|
||||
MP_MOUSE_RIGHT = 0x01,
|
||||
MP_MOUSE_MIDDLE = 0x02,
|
||||
MP_MOUSE_EXT1 = 0x03,
|
||||
MP_MOUSE_EXT2 = 0x04;
|
||||
|
||||
static const u32 MP_KEY_COUNT = MP_KEY_MAX+1,
|
||||
MP_MOUSE_BUTTON_COUNT = 5;
|
||||
|
||||
typedef struct mp_key_event // keyboard and mouse buttons input
|
||||
{
|
||||
mp_key_action action;
|
||||
mp_key_code code;
|
||||
mp_key_mods mods;
|
||||
char label[8];
|
||||
u8 labelLen;
|
||||
int clickCount;
|
||||
} mp_key_event;
|
||||
|
||||
typedef struct mp_char_event // character input
|
||||
{
|
||||
utf32 codepoint;
|
||||
char sequence[8];
|
||||
u8 seqLen;
|
||||
} mp_char_event;
|
||||
|
||||
typedef struct mp_move_event // mouse move/scroll
|
||||
{
|
||||
f32 x;
|
||||
f32 y;
|
||||
f32 deltaX;
|
||||
f32 deltaY;
|
||||
mp_key_mods mods;
|
||||
} mp_move_event;
|
||||
|
||||
typedef struct mp_frame_event // window resize / move
|
||||
{
|
||||
mp_rect rect;
|
||||
} mp_frame_event;
|
||||
|
||||
typedef struct mp_event
|
||||
{
|
||||
//TODO clipboard and path drop
|
||||
|
||||
mp_window window;
|
||||
mp_event_type type;
|
||||
|
||||
union
|
||||
{
|
||||
mp_key_event key;
|
||||
mp_char_event character;
|
||||
mp_move_event move;
|
||||
mp_frame_event frame;
|
||||
};
|
||||
|
||||
//TODO(martin): chain externally ?
|
||||
list_elt list;
|
||||
} mp_event;
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// app management
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
void mp_init();
|
||||
void mp_terminate();
|
||||
|
||||
bool mp_should_quit();
|
||||
void mp_do_quit();
|
||||
void mp_request_quit();
|
||||
void mp_cancel_quit();
|
||||
|
||||
void mp_set_cursor(mp_mouse_cursor cursor);
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// window management
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
//#include"graphics.h"
|
||||
|
||||
bool mp_window_handle_is_null(mp_window window);
|
||||
mp_window mp_window_null_handle();
|
||||
|
||||
mp_window mp_window_create(mp_rect contentRect, const char* title, mp_window_style style);
|
||||
void mp_window_destroy(mp_window window);
|
||||
|
||||
bool mp_window_should_close(mp_window window);
|
||||
void mp_window_request_close(mp_window window);
|
||||
void mp_window_cancel_close(mp_window window);
|
||||
|
||||
void* mp_window_native_pointer(mp_window window);
|
||||
|
||||
void mp_window_center(mp_window window);
|
||||
|
||||
bool mp_window_is_hidden(mp_window window);
|
||||
bool mp_window_is_focused(mp_window window);
|
||||
|
||||
void mp_window_hide(mp_window window);
|
||||
void mp_window_focus(mp_window window);
|
||||
void mp_window_send_to_back(mp_window window);
|
||||
void mp_window_bring_to_front(mp_window window);
|
||||
|
||||
void mp_window_bring_to_front_and_focus(mp_window window);
|
||||
|
||||
mp_rect mp_window_content_rect_for_frame_rect(mp_rect frameRect, mp_window_style style);
|
||||
mp_rect mp_window_frame_rect_for_content_rect(mp_rect contentRect, mp_window_style style);
|
||||
|
||||
mp_rect mp_window_get_content_rect(mp_window window);
|
||||
mp_rect mp_window_get_absolute_content_rect(mp_window window);
|
||||
mp_rect mp_window_get_frame_rect(mp_window window);
|
||||
|
||||
void mp_window_set_content_rect(mp_window window, mp_rect contentRect);
|
||||
void mp_window_set_frame_rect(mp_window window, mp_rect frameRect);
|
||||
void mp_window_set_frame_size(mp_window window, int width, int height);
|
||||
void mp_window_set_content_size(mp_window window, int width, int height);
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// View management
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
mp_view mp_view_nil();
|
||||
bool mp_view_is_nil(mp_view view);
|
||||
mp_view mp_view_create(mp_window window, mp_rect frame);
|
||||
void mp_view_destroy(mp_view view);
|
||||
void mp_view_set_frame(mp_view view, mp_rect frame);
|
||||
|
||||
/*TODO
|
||||
mp_view mp_view_bring_to_front(mp_view view);
|
||||
mp_view mp_view_send_to_back(mp_view view);
|
||||
*/
|
||||
//--------------------------------------------------------------------
|
||||
// Main loop throttle
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
void mp_set_target_fps(u32 fps); // or use wait vblank?
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Events handling
|
||||
//--------------------------------------------------------------------
|
||||
void mp_pump_events(f64 timeout);
|
||||
bool mp_next_event(mp_event* event);
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Input state polling
|
||||
//--------------------------------------------------------------------
|
||||
bool mp_input_key_down(mp_key_code key);
|
||||
bool mp_input_key_pressed(mp_key_code key);
|
||||
bool mp_input_key_released(mp_key_code key);
|
||||
mp_key_mods mp_input_key_mods();
|
||||
|
||||
str8 mp_key_to_label(mp_key_code key);
|
||||
mp_key_code mp_label_to_key(str8 label);
|
||||
|
||||
bool mp_input_mouse_down(mp_mouse_button button);
|
||||
bool mp_input_mouse_pressed(mp_mouse_button button);
|
||||
bool mp_input_mouse_released(mp_mouse_button button);
|
||||
bool mp_input_mouse_clicked(mp_mouse_button button);
|
||||
bool mp_input_mouse_double_clicked(mp_mouse_button button);
|
||||
|
||||
vec2 mp_input_mouse_position();
|
||||
vec2 mp_input_mouse_delta();
|
||||
vec2 mp_input_mouse_wheel();
|
||||
|
||||
str32 mp_input_text_utf32(mem_arena* arena);
|
||||
str8 mp_input_text_utf8(mem_arena* arena);
|
||||
//--------------------------------------------------------------------
|
||||
// app resources
|
||||
//--------------------------------------------------------------------
|
||||
int mp_app_get_resource_path(const char* name, char** result);
|
||||
str8 mp_app_get_executable_path(mem_arena* arena);
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Clipboard
|
||||
//--------------------------------------------------------------------
|
||||
void mp_clipboard_clear();
|
||||
|
||||
void mp_clipboard_set_string(str8 string);
|
||||
str8 mp_clipboard_get_string(mem_arena* arena);
|
||||
str8 mp_clipboard_copy_string(str8 backing);
|
||||
|
||||
bool mp_clipboard_has_tag(const char* tag);
|
||||
void mp_clipboard_set_data_for_tag(const char* tag, str8 data);
|
||||
str8 mp_clipboard_get_data_for_tag(mem_arena* arena, const char* tag);
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// native open/save/alert windows
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
str8 mp_open_dialog(mem_arena* arena,
|
||||
const char* title,
|
||||
const char* defaultPath,
|
||||
int filterCount,
|
||||
const char** filters,
|
||||
bool directory);
|
||||
|
||||
str8 mp_save_dialog(mem_arena* arena,
|
||||
const char* title,
|
||||
const char* defaultPath,
|
||||
int filterCount,
|
||||
const char** filters);
|
||||
|
||||
int mp_alert_popup(const char* title,
|
||||
const char* message,
|
||||
u32 count,
|
||||
const char** options);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif //__PLATFORM_APP_H_
|
|
@ -0,0 +1,58 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: osx_app.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 12/02/2021
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __OSX_APP_H_
|
||||
#define __OSX_APP_H_
|
||||
|
||||
#import<Cocoa/Cocoa.h>
|
||||
#import<Carbon/Carbon.h>
|
||||
#include"mp_app.h"
|
||||
#include"graphics.h"
|
||||
|
||||
struct mp_window_data
|
||||
{
|
||||
list_elt freeListElt;
|
||||
u32 generation;
|
||||
|
||||
NSWindow* nsWindow;
|
||||
NSView* nsView;
|
||||
NSObject* nsWindowDelegate;
|
||||
|
||||
mp_rect contentRect;
|
||||
mp_rect frameRect;
|
||||
mp_window_style style;
|
||||
|
||||
bool shouldClose; //TODO could be in status flags
|
||||
bool hidden;
|
||||
|
||||
mp_view mainView;
|
||||
};
|
||||
|
||||
struct mp_view_data
|
||||
{
|
||||
list_elt freeListElt;
|
||||
u32 generation;
|
||||
|
||||
mp_window window;
|
||||
NSView* nsView;
|
||||
mg_surface surface;
|
||||
};
|
||||
|
||||
@interface MPNativeWindow : NSWindow
|
||||
{
|
||||
mp_window_data* mpWindow;
|
||||
}
|
||||
- (id)initWithMPWindow:(mp_window_data*) window contentRect:(NSRect) rect styleMask:(uint32) style;
|
||||
@end
|
||||
|
||||
mp_window_data* mp_window_ptr_from_handle(mp_window handle);
|
||||
mp_view_data* mp_view_ptr_from_handle(mp_view handle);
|
||||
|
||||
|
||||
|
||||
#endif //__OSX_APP_H_
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,193 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: linux_clock.c
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 20/04/2020
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
|
||||
#include<math.h> //fabs()
|
||||
#include<time.h>
|
||||
#include<sys/time.h> // gettimeofday()
|
||||
|
||||
#include<sys/types.h>
|
||||
#include<sys/sysctl.h>
|
||||
|
||||
#include<unistd.h> // nanosleep()
|
||||
|
||||
#include"platform_rng.h"
|
||||
#include"platform_clock.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
//TODO(martin): measure the actual values of these constants
|
||||
const f64 SYSTEM_FUZZ = 25e-9, // minimum time to read the clock (s)
|
||||
SYSTEM_TICK = 25e-9; // minimum step between two clock readings (s)
|
||||
|
||||
static u64 __initialTimestamp__ = 0;
|
||||
static u64 __initialMonotonicNanoseconds__ = 0;
|
||||
|
||||
static inline u64 LinuxGetMonotonicNanoseconds()
|
||||
{
|
||||
timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
|
||||
//WARN(martin): do not multiply ts.tv_sec directly (implicit conversion will overflow)
|
||||
u64 r = ts.tv_sec;
|
||||
r *= 1000000000;
|
||||
r += ts.tv_nsec;
|
||||
return(r);
|
||||
}
|
||||
|
||||
static inline u64 LinuxGetUptimeNanoseconds()
|
||||
{
|
||||
timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
//WARN(martin): do not multiply ts.tv_sec directly (implicit conversion will overflow)
|
||||
u64 r = ts.tv_sec;
|
||||
r *= 1000000000;
|
||||
r += ts.tv_nsec;
|
||||
return(r);
|
||||
}
|
||||
|
||||
|
||||
void ClockSystemInit()
|
||||
{
|
||||
//NOTE(martin): we don't know of a widely supported way of getting the boot time on linux, so
|
||||
// we fallback to our second best choice, which is taking an initial timestamp and
|
||||
// the initial monotonic time now
|
||||
|
||||
timeval tv;
|
||||
gettimeofday(&tv, 0);
|
||||
__initialMonotonicNanoseconds__ = LinuxGetMonotonicNanoseconds();
|
||||
|
||||
//NOTE(martin): convert boot date to timestamp
|
||||
__initialTimestamp__ = (((u64)tv.tv_sec + JAN_1970) << 32)
|
||||
+ (u64)(tv.tv_usec * 1e-6 * TIMESTAMPS_PER_SECOND);
|
||||
|
||||
//TODO(martin): maybe get a state vector for exclusive clock usage ?
|
||||
RandomSeedFromDevice();
|
||||
}
|
||||
|
||||
fx_timestamp ClockGetTimestamp(clock_kind clock)
|
||||
{
|
||||
fx_timestamp ts = {0};
|
||||
switch(clock)
|
||||
{
|
||||
case SYS_CLOCK_MONOTONIC:
|
||||
{
|
||||
//NOTE(martin): compute monotonic offset and add it to bootup timestamp
|
||||
u64 noff = LinuxGetMonotonicNanoseconds() - __initialMonotonicNanoseconds__;
|
||||
u64 foff = (u64)(noff * 1e-9 * TIMESTAMPS_PER_SECOND);
|
||||
ts.ts = __initialTimestamp__ + foff;
|
||||
} break;
|
||||
|
||||
case SYS_CLOCK_UPTIME:
|
||||
{
|
||||
//TODO(martin): maybe we should warn that this date is inconsistent after a sleep ?
|
||||
//NOTE(martin): compute uptime offset and add it to bootup timestamp
|
||||
u64 noff = LinuxGetUptimeNanoseconds() - __initialMonotonicNanoseconds__ ;
|
||||
u64 foff = (u64)(noff * 1e-9 * TIMESTAMPS_PER_SECOND);
|
||||
ts.ts = __initialTimestamp__ + foff;
|
||||
} break;
|
||||
|
||||
case SYS_CLOCK_DATE:
|
||||
{
|
||||
//NOTE(martin): get system date and convert it to a fixed-point timestamp
|
||||
timeval tv;
|
||||
gettimeofday(&tv, 0);
|
||||
ts.ts = (((u64)tv.tv_sec + JAN_1970) << 32)
|
||||
+ (u64)(tv.tv_usec * 1e-6 * TIMESTAMPS_PER_SECOND);
|
||||
} break;
|
||||
}
|
||||
|
||||
//NOTE(martin): add a random fuzz between 0 and 1 times the system fuzz
|
||||
f64 fuzz = RandomU32()/(f64)(~(0UL)) * SYSTEM_FUZZ;
|
||||
fx_timediff tdfuzz = TimediffFromSeconds(fuzz);
|
||||
ts = TimestampAdd(ts, tdfuzz);
|
||||
//TODO(martin): ensure that we always return a value greater than the last value
|
||||
|
||||
return(ts);
|
||||
}
|
||||
|
||||
|
||||
f64 ClockGetTime(clock_kind clock)
|
||||
{
|
||||
switch(clock)
|
||||
{
|
||||
case SYS_CLOCK_MONOTONIC:
|
||||
{
|
||||
//NOTE(martin): compute monotonic offset and add it to bootup timestamp
|
||||
u64 noff = LinuxGetMonotonicNanoseconds();
|
||||
return((f64)noff * 1e-9);
|
||||
} break;
|
||||
|
||||
case SYS_CLOCK_UPTIME:
|
||||
{
|
||||
//TODO(martin): maybe we should warn that this date is inconsistent after a sleep ?
|
||||
//NOTE(martin): compute uptime offset and add it to bootup timestamp
|
||||
u64 noff = LinuxGetUptimeNanoseconds();
|
||||
return((f64)noff * 1e-9);
|
||||
} break;
|
||||
|
||||
case SYS_CLOCK_DATE:
|
||||
{
|
||||
//TODO(martin): maybe warn about precision loss ?
|
||||
// could also change the epoch since we only promise to return a relative time
|
||||
//NOTE(martin): get system date and convert it to seconds
|
||||
timeval tv;
|
||||
gettimeofday(&tv, 0);
|
||||
return(((f64)tv.tv_sec + JAN_1970) + ((f64)tv.tv_usec * 1e-6));
|
||||
} break;
|
||||
}
|
||||
return(0);
|
||||
}
|
||||
|
||||
void ClockSleepNanoseconds(u64 nanoseconds)
|
||||
{
|
||||
timespec rqtp;
|
||||
rqtp.tv_sec = nanoseconds / 1000000000;
|
||||
rqtp.tv_nsec = nanoseconds - rqtp.tv_sec * 1000000000;
|
||||
nanosleep(&rqtp, 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//TODO: update these functions for various clocks besides monotonic
|
||||
////////////////////////////////////////////////////////////////////
|
||||
f64 ClockGetGranularity(clock_kind clock)
|
||||
{
|
||||
u64 minDiff = ~(0ULL);
|
||||
|
||||
const int GRANULARITY_NUM_ITERATION = 100000;
|
||||
for(int i=0; i<GRANULARITY_NUM_ITERATION; i++)
|
||||
{
|
||||
u64 a = LinuxGetMonotonicNanoseconds();
|
||||
u64 b = LinuxGetMonotonicNanoseconds();
|
||||
u64 diff = b - a;
|
||||
if(diff != 0 && diff < minDiff)
|
||||
{
|
||||
minDiff = diff;
|
||||
}
|
||||
}
|
||||
return(minDiff * 1e-9);
|
||||
}
|
||||
|
||||
f64 ClockGetMeanReadTime(clock_kind clock)
|
||||
{
|
||||
u64 start = LinuxGetMonotonicNanoseconds();
|
||||
fx_timestamp time;
|
||||
const int READ_NUM_ITERATION = 1000000;
|
||||
for(int i=0; i<READ_NUM_ITERATION; i++)
|
||||
{
|
||||
time = ClockGetTimestamp(SYS_CLOCK_MONOTONIC);
|
||||
}
|
||||
volatile u64 ts = time.ts;
|
||||
u64 end = LinuxGetMonotonicNanoseconds();
|
||||
f64 mean = (end - start)/(f64)READ_NUM_ITERATION;
|
||||
|
||||
return(mean * 1e-9);
|
||||
}
|
||||
|
||||
} // extern "C"
|
|
@ -0,0 +1,176 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: osx_clock.cpp
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 07/03/2019
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
|
||||
#include<math.h> //fabs()
|
||||
#include<time.h>
|
||||
#include<sys/time.h> // gettimeofday()
|
||||
#include<mach/mach.h>
|
||||
#include<mach/mach_time.h>
|
||||
#include<mach/clock.h>
|
||||
#include<Availability.h> // availability macros
|
||||
|
||||
#include<sys/types.h>
|
||||
#include<sys/sysctl.h>
|
||||
|
||||
#include<unistd.h> // nanosleep()
|
||||
|
||||
#include"platform_clock.h"
|
||||
|
||||
|
||||
typedef struct timeval timeval;
|
||||
typedef struct timespec timespec;
|
||||
|
||||
#define LOG_SUBSYSTEM "Platform"
|
||||
|
||||
//TODO(martin): measure the actual values of these constants
|
||||
const f64 SYSTEM_FUZZ = 25e-9, // minimum time to read the clock (s)
|
||||
SYSTEM_TICK = 25e-9; // minimum step between two clock readings (s)
|
||||
|
||||
static mach_timebase_info_data_t __machTimeBase__ = {1,1};
|
||||
static u64 __initialTimestamp__ = 0;
|
||||
|
||||
static inline u64 OSXGetUptimeNanoseconds()
|
||||
{
|
||||
//NOTE(martin): according to the documentation, mach_absolute_time() does not
|
||||
// increment when the system is asleep
|
||||
u64 now = mach_absolute_time();
|
||||
now *= __machTimeBase__.numer;
|
||||
now /= __machTimeBase__.denom;
|
||||
return(now);
|
||||
}
|
||||
|
||||
static inline u64 OSXGetMonotonicNanoseconds()
|
||||
{
|
||||
//NOTE(martin): according to the documentation, MP_CLOCK_MONOTONIC increment monotonically
|
||||
// on systems where MP_CLOCK_MONOTONIC_RAW is present, we may want to use that instead,
|
||||
// because MP_CLOCK_MONOTONIC seems to be subject to frequency changes ?
|
||||
|
||||
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200
|
||||
#ifndef CLOCK_MONOTONIC_RAW
|
||||
#error "CLOCK_MONOTONIC_RAW not found. Please verify that <time.h> is included from the MacOSX SDK rather than /usr/local/include"
|
||||
#else
|
||||
return(clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW));
|
||||
#endif
|
||||
#else
|
||||
//TODO(martin): quick and dirty hack is to fallback to uptime,
|
||||
// but we should either only support macos version >= 10.12, or find a proper solution
|
||||
return(OSXGetUptimeNanoseconds());
|
||||
#endif
|
||||
}
|
||||
|
||||
static const f64 CLK_TIMESTAMPS_PER_SECOND = 4294967296.; // 2^32 as a double
|
||||
static const u64 CLK_JAN_1970 = 2208988800ULL; // seconds from january 1900 to january 1970
|
||||
|
||||
void mp_clock_init()
|
||||
{
|
||||
mach_timebase_info(&__machTimeBase__);
|
||||
|
||||
//NOTE(martin): get the date of system boot time
|
||||
timeval tv = {0, 0};
|
||||
|
||||
int mib[2] = {CTL_KERN,
|
||||
KERN_BOOTTIME};
|
||||
size_t size = sizeof(tv);
|
||||
|
||||
if(sysctl(mib, 2, &tv, &size, 0, 0) == -1)
|
||||
{
|
||||
LOG_ERROR("can't read boot time\n");
|
||||
}
|
||||
//NOTE(martin): convert boot date to timestamp
|
||||
__initialTimestamp__ = (((u64)tv.tv_sec + CLK_JAN_1970) << 32)
|
||||
+ (u64)(tv.tv_usec * 1e-6 * CLK_TIMESTAMPS_PER_SECOND);
|
||||
|
||||
//TODO(martin): maybe get a state vector for exclusive clock usage ?
|
||||
//RandomSeedFromDevice();
|
||||
}
|
||||
|
||||
u64 mp_get_timestamp(mp_clock_kind clock)
|
||||
{
|
||||
u64 ts = 0;
|
||||
switch(clock)
|
||||
{
|
||||
case MP_CLOCK_MONOTONIC:
|
||||
{
|
||||
//NOTE(martin): compute monotonic offset and add it to bootup timestamp
|
||||
u64 noff = OSXGetMonotonicNanoseconds() ;
|
||||
u64 foff = (u64)(noff * 1e-9 * CLK_TIMESTAMPS_PER_SECOND);
|
||||
ts = __initialTimestamp__ + foff;
|
||||
} break;
|
||||
|
||||
case MP_CLOCK_UPTIME:
|
||||
{
|
||||
//TODO(martin): maybe we should warn that this date is inconsistent after a sleep ?
|
||||
//NOTE(martin): compute uptime offset and add it to bootup timestamp
|
||||
u64 noff = OSXGetUptimeNanoseconds() ;
|
||||
u64 foff = (u64)(noff * 1e-9 * CLK_TIMESTAMPS_PER_SECOND);
|
||||
ts = __initialTimestamp__ + foff;
|
||||
} break;
|
||||
|
||||
case MP_CLOCK_DATE:
|
||||
{
|
||||
//NOTE(martin): get system date and convert it to a fixed-point timestamp
|
||||
timeval tv;
|
||||
gettimeofday(&tv, 0);
|
||||
ts = (((u64)tv.tv_sec + CLK_JAN_1970) << 32)
|
||||
+ (u64)(tv.tv_usec * 1e-6 * CLK_TIMESTAMPS_PER_SECOND);
|
||||
} break;
|
||||
}
|
||||
|
||||
/*
|
||||
//NOTE(martin): add a random fuzz between 0 and 1 times the system fuzz
|
||||
//TODO(martin): ensure that we always return a value greater than the last value
|
||||
f64 fuzz = RandomU32()/(f64)(~(0UL)) * SYSTEM_FUZZ;
|
||||
ts_timediff tdfuzz = TimediffFromSeconds(fuzz);
|
||||
ts = TimestampAdd(ts, tdfuzz);
|
||||
*/
|
||||
|
||||
return(ts);
|
||||
}
|
||||
|
||||
|
||||
f64 mp_get_time(mp_clock_kind clock)
|
||||
{
|
||||
switch(clock)
|
||||
{
|
||||
case MP_CLOCK_MONOTONIC:
|
||||
{
|
||||
//NOTE(martin): compute monotonic offset and add it to bootup timestamp
|
||||
u64 noff = OSXGetMonotonicNanoseconds();
|
||||
return((f64)noff * 1e-9);
|
||||
} break;
|
||||
|
||||
case MP_CLOCK_UPTIME:
|
||||
{
|
||||
//TODO(martin): maybe we should warn that this date is inconsistent after a sleep ?
|
||||
//NOTE(martin): compute uptime offset and add it to bootup timestamp
|
||||
u64 noff = OSXGetUptimeNanoseconds();
|
||||
return((f64)noff * 1e-9);
|
||||
} break;
|
||||
|
||||
case MP_CLOCK_DATE:
|
||||
{
|
||||
//TODO(martin): maybe warn about precision loss ?
|
||||
// could also change the epoch since we only promise to return a relative time
|
||||
//NOTE(martin): get system date and convert it to seconds
|
||||
timeval tv;
|
||||
gettimeofday(&tv, 0);
|
||||
return(((f64)tv.tv_sec + CLK_JAN_1970) + ((f64)tv.tv_usec * 1e-6));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void mp_sleep_nanoseconds(u64 nanoseconds)
|
||||
{
|
||||
timespec rqtp;
|
||||
rqtp.tv_sec = nanoseconds / 1000000000;
|
||||
rqtp.tv_nsec = nanoseconds - rqtp.tv_sec * 1000000000;
|
||||
nanosleep(&rqtp, 0);
|
||||
}
|
||||
|
||||
#undef LOG_SUBSYSTEM
|
|
@ -0,0 +1,28 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: platform_base_allocator.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 10/09/2021
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __PLATFORM_BASE_ALLOCATOR_H_
|
||||
#define __PLATFORM_BASE_ALLOCATOR_H_
|
||||
|
||||
#include"typedefs.h"
|
||||
#include"memory.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void* mem_base_reserve_mmap(void* context, u64 size);
|
||||
void mem_base_release_mmap(void* context, void* ptr, u64 size);
|
||||
|
||||
mem_base_allocator* mem_base_allocator_default();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif //__PLATFORM_BASE_ALLOCATOR_H_
|
|
@ -0,0 +1,34 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: platform_clock.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 07/03/2019
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __PLATFORM_CLOCK_H_
|
||||
#define __PLATFORM_CLOCK_H_
|
||||
|
||||
#include"typedefs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
typedef enum {
|
||||
MP_CLOCK_MONOTONIC, // clock that increment monotonically
|
||||
MP_CLOCK_UPTIME, // clock that increment monotonically during uptime
|
||||
MP_CLOCK_DATE // clock that is driven by the platform time
|
||||
} mp_clock_kind;
|
||||
|
||||
void mp_clock_init(); // initialize the clock subsystem
|
||||
u64 mp_get_timestamp(mp_clock_kind clock);
|
||||
f64 mp_get_time(mp_clock_kind clock);
|
||||
void mp_sleep_nanoseconds(u64 nanoseconds); // sleep for a given number of nanoseconds
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif // __cplusplus
|
||||
|
||||
|
||||
#endif //__PLATFORM_CLOCK_H_
|
|
@ -0,0 +1,18 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: platform_rng.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 06/03/2020
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __PLATFORM_RANDOM_H_
|
||||
#define __PLATFORM_RANDOM_H_
|
||||
|
||||
#include"typedefs.h"
|
||||
|
||||
int RandomSeedFromDevice();
|
||||
u32 RandomU32();
|
||||
u64 RandomU64();
|
||||
|
||||
#endif //__PLATFORM_RANDOM_H_
|
|
@ -0,0 +1,161 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: platform_socket.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 22/03/2019
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __PLATFORM_SOCKET_H_
|
||||
#define __PLATFORM_SOCKET_H_
|
||||
|
||||
#include<sys/time.h> // timeval
|
||||
#include"typedefs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#else
|
||||
typedef struct timeval timeval;
|
||||
#endif //__cplusplus
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Errors
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
//TODO(martin): extend these error codes
|
||||
const int SOCK_ERR_OK = 0,
|
||||
SOCK_ERR_UNKNOWN = -1,
|
||||
SOCK_ERR_ACCESS = -2,
|
||||
SOCK_ERR_MEM = -3,
|
||||
SOCK_ERR_INTR = -4,
|
||||
SOCK_ERR_USED = -5,
|
||||
SOCK_ERR_BADF = -6,
|
||||
SOCK_ERR_ABORT = -7,
|
||||
SOCK_ERR_NBLOCK = -8;
|
||||
|
||||
int SocketGetLastError();
|
||||
const char* SocketGetLastErrorMessage();
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Addresses
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
//NOTE(martin): net_ip and net_port are stored in network byte order
|
||||
// host_ip and host_port are stored in host byte order
|
||||
typedef uint32 net_ip;
|
||||
typedef uint16 net_port;
|
||||
typedef uint32 host_ip;
|
||||
typedef uint16 host_port;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
net_ip ip;
|
||||
net_port port;
|
||||
} socket_address;
|
||||
|
||||
//NOTE(martin): these are in host byte order !
|
||||
const host_ip SOCK_IP_LOOPBACK = 0x7f000001;
|
||||
const host_ip SOCK_IP_ANY = 0;
|
||||
const host_port SOCK_PORT_ANY = 0;
|
||||
|
||||
net_ip StringToNetIP(const char* addr);
|
||||
const char* NetIPToString(net_ip ip);
|
||||
|
||||
host_ip StringToHostIP(const char* addr);
|
||||
const char* HostIPToString(host_ip ip);
|
||||
|
||||
net_ip HostToNetIP(host_ip ip);
|
||||
net_port HostToNetPort(host_port port);
|
||||
host_ip NetToHostIP(net_ip ip);
|
||||
host_port NetToHostPort(net_port port);
|
||||
|
||||
|
||||
int SocketGetIFAddresses(int* count, net_ip* ips);
|
||||
net_ip SocketGetDefaultExternalIP();
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Socket API
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
typedef struct platform_socket platform_socket;
|
||||
|
||||
typedef enum { SOCK_UDP, SOCK_TCP } socket_transport;
|
||||
|
||||
const int SOCK_MSG_OOB = 0x01,
|
||||
SOCK_MSG_PEEK = 0x02,
|
||||
SOCK_MSG_DONTROUTE = 0x04,
|
||||
SOCK_MSG_WAITALL = 0x40;
|
||||
|
||||
platform_socket* SocketOpen(socket_transport transport);
|
||||
int SocketClose(platform_socket* socket);
|
||||
|
||||
int SocketBind(platform_socket* socket, socket_address* addr);
|
||||
int SocketListen(platform_socket* socket, int backlog);
|
||||
platform_socket* SocketAccept(platform_socket* socket, socket_address* from);
|
||||
|
||||
int SocketConnect(platform_socket* socket, socket_address* addr);
|
||||
|
||||
int64 SocketReceive(platform_socket* socket, void* buffer, uint64 size, int flags);
|
||||
int64 SocketReceiveFrom(platform_socket* socket, void* buffer, uint64 size, int flags, socket_address* from);
|
||||
|
||||
int64 SocketSend(platform_socket* socket, void* buffer, uint64 size, int flags);
|
||||
int64 SocketSendTo(platform_socket* socket, void* buffer, uint64 size, int flags, socket_address* to);
|
||||
|
||||
int SocketGetAddress(platform_socket* socket, socket_address* addr);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Multiplexing
|
||||
//----------------------------------------------------------------------------------
|
||||
const uint8 SOCK_ACTIVITY_IN = 1<<0,
|
||||
SOCK_ACTIVITY_OUT = 1<<2,
|
||||
SOCK_ACTIVITY_ERR = 1<<3;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
platform_socket* sock;
|
||||
uint8 watch;
|
||||
uint8 set;
|
||||
} socket_activity;
|
||||
|
||||
int SocketSelect(uint32 count, socket_activity* set, double timeout);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Socket Options
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
int SocketSetReceiveTimeout(platform_socket* socket, timeval* tv);
|
||||
int SocketSetSendTimeout(platform_socket* socket, timeval* tv);
|
||||
int SocketSetBroadcast(platform_socket* sock, bool enable);
|
||||
int SocketSetReuseAddress(platform_socket* sock, bool enable);
|
||||
int SocketSetReusePort(platform_socket* sock, bool enable);
|
||||
int SocketSetReceiveTimestamping(platform_socket* socket, bool enable);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Multicast
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
int SocketSetMulticastLoop(platform_socket* sock, bool enable);
|
||||
int SocketJoinMulticastGroup(platform_socket* socket, host_ip group, host_ip interface);
|
||||
int SocketLeaveMulticastGroup(platform_socket* socket, host_ip group, host_ip interface);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
//Ancillary data API
|
||||
//----------------------------------------------------------------------------------
|
||||
typedef struct
|
||||
{
|
||||
u64 messageBufferSize;
|
||||
char* messageBuffer;
|
||||
|
||||
u64 controlBufferSize;
|
||||
char* controlBuffer;
|
||||
} socket_msg;
|
||||
|
||||
int SocketReceiveMessage(platform_socket* socket, socket_msg* msg, socket_address* from);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
|
||||
#endif //__PLATFORM_SOCKET_H_
|
|
@ -0,0 +1,88 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: platform_thread.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 21/03/2019
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __PLATFORM_THREAD_H_
|
||||
#define __PLATFORM_THREAD_H_
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include<atomic>
|
||||
#define _Atomic(T) std::atomic<T>
|
||||
#else
|
||||
#include<stdatomic.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Platform Thread API
|
||||
//---------------------------------------------------------------
|
||||
|
||||
typedef struct platform_thread platform_thread;
|
||||
|
||||
typedef void* (*ThreadStartFunction)(void* userPointer);
|
||||
|
||||
platform_thread* ThreadCreate(ThreadStartFunction start, void* userPointer);
|
||||
platform_thread* ThreadCreateWithName(ThreadStartFunction start, void* userPointer, const char* name);
|
||||
|
||||
const char* ThreadGetName(platform_thread* thread);
|
||||
|
||||
u64 ThreadSelfID();
|
||||
u64 ThreadUniqueID(platform_thread* thread);
|
||||
|
||||
int ThreadSignal(platform_thread* thread, int sig);
|
||||
void ThreadCancel(platform_thread* thread);
|
||||
int ThreadJoin(platform_thread* thread, void** ret);
|
||||
int ThreadDetach(platform_thread* thread);
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Platform Mutex API
|
||||
//---------------------------------------------------------------
|
||||
|
||||
typedef struct platform_mutex platform_mutex;
|
||||
|
||||
platform_mutex* MutexCreate();
|
||||
int MutexDestroy(platform_mutex* mutex);
|
||||
int MutexLock(platform_mutex* mutex);
|
||||
int MutexUnlock(platform_mutex* mutex);
|
||||
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Lightweight ticket mutex API
|
||||
//---------------------------------------------------------------
|
||||
|
||||
typedef struct ticket_spin_mutex
|
||||
{
|
||||
volatile _Atomic(u64) nextTicket;
|
||||
volatile _Atomic(u64) serving;
|
||||
} ticket_spin_mutex;
|
||||
|
||||
void TicketSpinMutexInit(ticket_spin_mutex* mutex);
|
||||
void TicketSpinMutexLock(ticket_spin_mutex* mutex);
|
||||
void TicketSpinMutexUnlock(ticket_spin_mutex* mutex);
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Platform condition variable API
|
||||
//---------------------------------------------------------------
|
||||
|
||||
typedef struct platform_condition platform_condition;
|
||||
|
||||
platform_condition* ConditionCreate();
|
||||
int ConditionDestroy(platform_condition* cond);
|
||||
int ConditionWait(platform_condition* cond, platform_mutex* mutex);
|
||||
int ConditionTimedWait(platform_condition* cond, platform_mutex* mutex, f64 seconds);
|
||||
int ConditionSignal(platform_condition* cond);
|
||||
int ConditionBroadcast(platform_condition* cond);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif //__PLATFORM_THREAD_H_
|
|
@ -0,0 +1,515 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: posix_socket.c
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 22/03/2019
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
|
||||
#include<sys/socket.h> // socket()
|
||||
#include<netinet/ip.h> // socaddr_in
|
||||
#include<arpa/inet.h> // inet_addr()
|
||||
#include<ifaddrs.h> // getifaddrs() / freeifaddrs()
|
||||
#include<unistd.h> // close()
|
||||
#include<string.h> // strerror()
|
||||
#include<errno.h> // errno
|
||||
#include<stdlib.h> // malloc()/free()
|
||||
|
||||
#include"platform_socket.h"
|
||||
#include"debug_log.h"
|
||||
|
||||
typedef struct in_addr in_addr;
|
||||
typedef struct sockaddr_in sockaddr_in;
|
||||
typedef struct sockaddr sockaddr;
|
||||
typedef struct msghdr msghdr;
|
||||
typedef struct cmsghdr cmsghdr;
|
||||
typedef struct ip_mreq ip_mreq;
|
||||
typedef struct iovec iovec;
|
||||
|
||||
#define LOG_SUBSYSTEM "Platform"
|
||||
|
||||
net_ip StringToNetIP(const char* addr)
|
||||
{
|
||||
return(inet_addr(addr));
|
||||
}
|
||||
const char* NetIPToString(net_ip ip)
|
||||
{
|
||||
in_addr in;
|
||||
in.s_addr = ip;
|
||||
return(inet_ntoa(in));
|
||||
}
|
||||
|
||||
host_ip StringToHostIP(const char* addr)
|
||||
{
|
||||
return(NetToHostIP(StringToNetIP(addr)));
|
||||
}
|
||||
const char* HostIPToString(host_ip ip)
|
||||
{
|
||||
return(NetIPToString(HostToNetIP(ip)));
|
||||
}
|
||||
|
||||
net_ip HostToNetIP(uint32 ip)
|
||||
{
|
||||
return(htonl(ip));
|
||||
}
|
||||
net_port HostToNetPort(uint16 port)
|
||||
{
|
||||
return(htons(port));
|
||||
}
|
||||
uint32 NetToHostIP(net_ip ip)
|
||||
{
|
||||
return(ntohl(ip));
|
||||
}
|
||||
uint16 NetToHostPort(net_port port)
|
||||
{
|
||||
return(ntohs(port));
|
||||
}
|
||||
|
||||
static int PlatformToSocketFlags(int flags)
|
||||
{
|
||||
int sflags = 0;
|
||||
if(flags & SOCK_MSG_OOB)
|
||||
{
|
||||
sflags |= MSG_OOB;
|
||||
}
|
||||
if(flags & SOCK_MSG_PEEK)
|
||||
{
|
||||
sflags |= MSG_PEEK;
|
||||
}
|
||||
if(flags & SOCK_MSG_DONTROUTE)
|
||||
{
|
||||
sflags |= MSG_DONTROUTE;
|
||||
}
|
||||
if(flags & SOCK_MSG_WAITALL)
|
||||
{
|
||||
sflags |= MSG_WAITALL;
|
||||
}
|
||||
|
||||
return(sflags);
|
||||
}
|
||||
|
||||
static int ErrnoToSocketError(int err)
|
||||
{
|
||||
//TODO(martin): extend these error codes
|
||||
switch(err)
|
||||
{
|
||||
case 0: return(SOCK_ERR_OK);
|
||||
case EACCES: return(SOCK_ERR_ACCESS);
|
||||
case ENOBUFS:
|
||||
case ENOMEM: return(SOCK_ERR_MEM);
|
||||
case EINTR: return(SOCK_ERR_INTR);
|
||||
case EADDRINUSE: return(SOCK_ERR_USED);
|
||||
case EBADF: return(SOCK_ERR_BADF);
|
||||
case ECONNABORTED: return(SOCK_ERR_ABORT);
|
||||
|
||||
case EWOULDBLOCK:
|
||||
#if EAGAIN != EWOULDBLOCK
|
||||
case EAGAIN:
|
||||
#endif
|
||||
return(SOCK_ERR_NBLOCK);
|
||||
|
||||
default:
|
||||
return(SOCK_ERR_UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
int SocketGetLastError()
|
||||
{
|
||||
return(ErrnoToSocketError(errno));
|
||||
}
|
||||
|
||||
const char* SocketGetLastErrorMessage()
|
||||
{
|
||||
return(strerror(errno));
|
||||
}
|
||||
|
||||
struct platform_socket
|
||||
{
|
||||
int sd;
|
||||
};
|
||||
|
||||
platform_socket* SocketOpen(socket_transport transport)
|
||||
{
|
||||
int sd = 0;
|
||||
switch(transport)
|
||||
{
|
||||
case SOCK_UDP:
|
||||
sd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
break;
|
||||
|
||||
case SOCK_TCP:
|
||||
sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
break;
|
||||
}
|
||||
if(!sd)
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
|
||||
platform_socket* sock = (platform_socket*)malloc(sizeof(platform_socket));
|
||||
if(!sock)
|
||||
{
|
||||
close(sd);
|
||||
return(0);
|
||||
}
|
||||
sock->sd = sd;
|
||||
return(sock);
|
||||
}
|
||||
|
||||
int SocketClose(platform_socket* sock)
|
||||
{
|
||||
if(!sock)
|
||||
{
|
||||
return(-1);
|
||||
}
|
||||
int res = close(sock->sd);
|
||||
free(sock);
|
||||
return(res);
|
||||
}
|
||||
|
||||
int SocketBind(platform_socket* sock, socket_address* addr)
|
||||
{
|
||||
sockaddr_in saddr;
|
||||
saddr.sin_addr.s_addr = addr->ip;
|
||||
saddr.sin_port = addr->port;
|
||||
saddr.sin_family = AF_INET;
|
||||
|
||||
return(bind(sock->sd, (sockaddr*)&saddr, sizeof(saddr)));
|
||||
}
|
||||
|
||||
int SocketListen(platform_socket* sock, int backlog)
|
||||
{
|
||||
return(listen(sock->sd, backlog));
|
||||
}
|
||||
platform_socket* SocketAccept(platform_socket* sock, socket_address* from)
|
||||
{
|
||||
sockaddr_in saddr;
|
||||
socklen_t saddrSize = sizeof(saddr);
|
||||
int sd = accept(sock->sd, (sockaddr*)&saddr, &saddrSize);
|
||||
|
||||
from->ip = saddr.sin_addr.s_addr;
|
||||
from->port = saddr.sin_port;
|
||||
|
||||
if(sd <= 0)
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
platform_socket* client = (platform_socket*)malloc(sizeof(platform_socket));
|
||||
if(!client)
|
||||
{
|
||||
close(sd);
|
||||
return(0);
|
||||
}
|
||||
client->sd = sd;
|
||||
return(client);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
int SocketConnect(platform_socket* sock, socket_address* addr)
|
||||
{
|
||||
sockaddr_in saddr;
|
||||
saddr.sin_addr.s_addr = addr->ip;
|
||||
saddr.sin_port = addr->port;
|
||||
saddr.sin_family = AF_INET;
|
||||
|
||||
return(connect(sock->sd, (sockaddr*)&saddr, sizeof(saddr)));
|
||||
}
|
||||
|
||||
int64 SocketReceive(platform_socket* sock, void* buffer, uint64 size, int flags)
|
||||
{
|
||||
return(recv(sock->sd, buffer, size, PlatformToSocketFlags(flags)));
|
||||
}
|
||||
|
||||
int64 SocketReceiveFrom(platform_socket* sock, void* buffer, uint64 size, int flags, socket_address* from)
|
||||
{
|
||||
sockaddr_in saddr;
|
||||
socklen_t saddrSize = sizeof(saddr);
|
||||
|
||||
int res = recvfrom(sock->sd, buffer, size, PlatformToSocketFlags(flags), (sockaddr*)&saddr, &saddrSize);
|
||||
|
||||
from->ip = saddr.sin_addr.s_addr;
|
||||
from->port = saddr.sin_port;
|
||||
return(res);
|
||||
}
|
||||
|
||||
int64 SocketSend(platform_socket* sock, void* buffer, uint64 size, int flags)
|
||||
{
|
||||
return(send(sock->sd, buffer, size, PlatformToSocketFlags(flags)));
|
||||
}
|
||||
int64 SocketSendTo(platform_socket* sock, void* buffer, uint64 size, int flags, socket_address* to)
|
||||
{
|
||||
sockaddr_in saddr;
|
||||
saddr.sin_addr.s_addr = to->ip;
|
||||
saddr.sin_port = to->port;
|
||||
saddr.sin_family = AF_INET;
|
||||
|
||||
return(sendto(sock->sd, buffer, size, PlatformToSocketFlags(flags), (sockaddr*)&saddr, sizeof(saddr)));
|
||||
}
|
||||
|
||||
|
||||
int SocketSetReceiveTimeout(platform_socket* sock, timeval* tv)
|
||||
{
|
||||
DEBUG_ASSERT(sock);
|
||||
DEBUG_ASSERT(sock->sd);
|
||||
return(setsockopt(sock->sd, SOL_SOCKET, SO_RCVTIMEO, tv, sizeof(timeval)));
|
||||
}
|
||||
|
||||
int SocketSetSendTimeout(platform_socket* sock, timeval* tv)
|
||||
{
|
||||
DEBUG_ASSERT(sock);
|
||||
DEBUG_ASSERT(sock->sd);
|
||||
return(setsockopt(sock->sd, SOL_SOCKET, SO_SNDTIMEO, tv, sizeof(timeval)));
|
||||
}
|
||||
|
||||
int SocketSetReceiveTimestamping(platform_socket* socket, bool enable)
|
||||
{
|
||||
int opt = enable ? 1 : 0;
|
||||
socklen_t len = sizeof(int);
|
||||
return(setsockopt(socket->sd, SOL_SOCKET, SO_TIMESTAMP, &enable, len));
|
||||
}
|
||||
|
||||
int SocketSetBroadcast(platform_socket* sock, bool enable)
|
||||
{
|
||||
DEBUG_ASSERT(sock);
|
||||
DEBUG_ASSERT(sock->sd);
|
||||
|
||||
int opt = enable ? 1 : 0;
|
||||
return(setsockopt(sock->sd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(int)));
|
||||
}
|
||||
|
||||
|
||||
int SocketSelect(uint32 count, socket_activity* set, double timeout)
|
||||
{
|
||||
fd_set fdInSet;
|
||||
fd_set fdOutSet;
|
||||
fd_set fdErrSet;
|
||||
FD_ZERO(&fdInSet);
|
||||
FD_ZERO(&fdOutSet);
|
||||
FD_ZERO(&fdErrSet);
|
||||
|
||||
int maxSd = -1;
|
||||
for(int i=0; i<count; i++)
|
||||
{
|
||||
socket_activity* item = &(set[i]);
|
||||
if(item->sock)
|
||||
{
|
||||
item->set = 0;
|
||||
if(item->watch & SOCK_ACTIVITY_IN)
|
||||
{
|
||||
FD_SET(item->sock->sd, &fdInSet);
|
||||
}
|
||||
if(item->watch & SOCK_ACTIVITY_OUT)
|
||||
{
|
||||
FD_SET(item->sock->sd, &fdOutSet);
|
||||
}
|
||||
if(item->watch & SOCK_ACTIVITY_ERR)
|
||||
{
|
||||
FD_SET(item->sock->sd, &fdErrSet);
|
||||
}
|
||||
|
||||
if(item->watch && (item->sock->sd > maxSd))
|
||||
{
|
||||
maxSd = item->sock->sd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(maxSd <= 0)
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
timeval tv;
|
||||
tv.tv_sec = (time_t)timeout;
|
||||
tv.tv_usec = (suseconds_t)((timeout - tv.tv_sec)*1000000);
|
||||
|
||||
timeval* ptv = timeout >= 0 ? &tv : 0;
|
||||
|
||||
int activity = select(maxSd+1, &fdInSet, &fdOutSet, &fdErrSet, ptv);
|
||||
if(activity < 0)
|
||||
{
|
||||
return(-1);
|
||||
}
|
||||
|
||||
int processed = 0;
|
||||
for(int i=0; i<count; i++)
|
||||
{
|
||||
if(processed >= activity)
|
||||
{
|
||||
break;
|
||||
}
|
||||
socket_activity* item = &(set[i]);
|
||||
if(item->sock)
|
||||
{
|
||||
if(FD_ISSET(item->sock->sd, &fdInSet))
|
||||
{
|
||||
item->set |= SOCK_ACTIVITY_IN;
|
||||
}
|
||||
if(FD_ISSET(item->sock->sd, &fdOutSet))
|
||||
{
|
||||
item->set |= SOCK_ACTIVITY_OUT;
|
||||
}
|
||||
if(FD_ISSET(item->sock->sd, &fdErrSet))
|
||||
{
|
||||
item->set |= SOCK_ACTIVITY_ERR;
|
||||
}
|
||||
if(item->set)
|
||||
{
|
||||
processed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return(activity);
|
||||
}
|
||||
|
||||
|
||||
int SocketGetAddress(platform_socket* sock, socket_address* addr)
|
||||
{
|
||||
sockaddr_in saddr;
|
||||
socklen_t sockLen = sizeof(saddr);
|
||||
if(getsockname(sock->sd, (sockaddr*)&saddr, &sockLen))
|
||||
{
|
||||
return(-1);
|
||||
}
|
||||
addr->ip = saddr.sin_addr.s_addr;
|
||||
addr->port = saddr.sin_port;
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
|
||||
int SocketGetIFAddresses(int* count, net_ip* ips)
|
||||
{
|
||||
struct ifaddrs* ifaList = 0;
|
||||
if(getifaddrs(&ifaList))
|
||||
{
|
||||
return(-1);
|
||||
}
|
||||
|
||||
int maxCount = *count;
|
||||
int i = 0;
|
||||
|
||||
for(struct ifaddrs* ifa = ifaList; ifa != 0 ; ifa = ifa->ifa_next)
|
||||
{
|
||||
if(i >= maxCount)
|
||||
{
|
||||
freeifaddrs(ifaList);
|
||||
*count = i;
|
||||
return(-1);
|
||||
}
|
||||
if(ifa->ifa_addr->sa_family == AF_INET)
|
||||
{
|
||||
struct in_addr in = ((sockaddr_in*)ifa->ifa_addr)->sin_addr;
|
||||
ips[i] = in.s_addr;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
freeifaddrs(ifaList);
|
||||
*count = i;
|
||||
return(0);
|
||||
}
|
||||
|
||||
|
||||
int SocketSetReuseAddress(platform_socket* sock, bool enable)
|
||||
{
|
||||
int reuse = enable ? 1 : 0;
|
||||
return(setsockopt(sock->sd, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)));
|
||||
}
|
||||
|
||||
int SocketSetReusePort(platform_socket* sock, bool enable)
|
||||
{
|
||||
int reuse = enable ? 1 : 0;
|
||||
return(setsockopt(sock->sd, SOL_SOCKET, SO_REUSEPORT, (char*)&reuse, sizeof(reuse)));
|
||||
}
|
||||
|
||||
int SocketSetMulticastLoop(platform_socket* sock, bool enable)
|
||||
{
|
||||
int on = enable ? 1 : 0;
|
||||
return(setsockopt(sock->sd, IPPROTO_IP, IP_MULTICAST_LOOP, (char*)&on, sizeof(on)));
|
||||
}
|
||||
|
||||
int SocketJoinMulticastGroup(platform_socket* sock, host_ip group, host_ip interface)
|
||||
{
|
||||
ip_mreq mreq;
|
||||
mreq.imr_multiaddr.s_addr = HostToNetIP(group);
|
||||
mreq.imr_interface.s_addr = HostToNetIP(interface);
|
||||
return(setsockopt(sock->sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq)));
|
||||
}
|
||||
|
||||
int SocketLeaveMulticastGroup(platform_socket* sock, host_ip group, host_ip interface)
|
||||
{
|
||||
ip_mreq mreq;
|
||||
mreq.imr_multiaddr.s_addr = HostToNetIP(group);
|
||||
mreq.imr_interface.s_addr = HostToNetIP(interface);
|
||||
return(setsockopt(sock->sd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&mreq, sizeof(mreq)));
|
||||
}
|
||||
|
||||
|
||||
net_ip SocketGetDefaultExternalIP()
|
||||
{
|
||||
//NOTE(martin): get the default local ip. This is a dumb way to do this
|
||||
// 'cause I can't be bothered to think of a better way right now :(
|
||||
|
||||
socket_address addr = {.ip = StringToNetIP("8.8.8.8"),
|
||||
.port = HostToNetPort(5001)};
|
||||
|
||||
platform_socket* sock = SocketOpen(SOCK_UDP);
|
||||
if(!sock)
|
||||
{
|
||||
LOG_ERROR("can't create socket");
|
||||
return(0);
|
||||
}
|
||||
|
||||
if(SocketConnect(sock, &addr) != 0)
|
||||
{
|
||||
LOG_ERROR("can't connect socket: %s\n", SocketGetLastErrorMessage());
|
||||
LOG_WARNING("try loopback interface\n");
|
||||
|
||||
addr.ip = HostToNetIP(SOCK_IP_LOOPBACK);
|
||||
if(SocketConnect(sock, &addr) != 0)
|
||||
{
|
||||
LOG_ERROR("can't connect socket: %s\n", SocketGetLastErrorMessage());
|
||||
SocketClose(sock);
|
||||
return(0);
|
||||
}
|
||||
}
|
||||
SocketGetAddress(sock, &addr);
|
||||
SocketClose(sock);
|
||||
return(addr.ip);
|
||||
}
|
||||
|
||||
int SocketReceiveMessage(platform_socket* socket, socket_msg* msg, socket_address* from)
|
||||
{
|
||||
sockaddr_in saddr;
|
||||
|
||||
iovec iov;
|
||||
iov.iov_base = msg->messageBuffer;
|
||||
iov.iov_len = msg->messageBufferSize;
|
||||
|
||||
msghdr hdr;
|
||||
hdr.msg_name = &saddr;
|
||||
hdr.msg_namelen = sizeof(saddr);
|
||||
hdr.msg_iov = &iov;
|
||||
hdr.msg_iovlen = 1;
|
||||
hdr.msg_control = msg->controlBuffer;
|
||||
hdr.msg_controllen = msg->controlBufferSize;
|
||||
|
||||
int size = recvmsg(socket->sd, &hdr, 0);
|
||||
if(size <= 0)
|
||||
{
|
||||
return(size);
|
||||
}
|
||||
|
||||
from->ip = saddr.sin_addr.s_addr;
|
||||
from->port = saddr.sin_port;
|
||||
|
||||
msg->controlBufferSize = hdr.msg_controllen;
|
||||
msg->messageBufferSize = iov.iov_len;
|
||||
|
||||
return(size);
|
||||
}
|
||||
|
||||
#undef LOG_SUBSYSTEM
|
|
@ -0,0 +1,241 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: posix_thread.c
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 21/03/2019
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#include<stdlib.h>
|
||||
#include<pthread.h>
|
||||
#include<signal.h> //needed for pthread_kill() on linux
|
||||
#include<string.h>
|
||||
#include<sys/time.h>
|
||||
|
||||
#include"platform_thread.h"
|
||||
|
||||
const u32 PLATFORM_THREAD_NAME_MAX_SIZE = 64; // including null terminator
|
||||
|
||||
struct platform_thread
|
||||
{
|
||||
bool valid;
|
||||
pthread_t pthread;
|
||||
ThreadStartFunction start;
|
||||
void* userPointer;
|
||||
char name[PLATFORM_THREAD_NAME_MAX_SIZE];
|
||||
};
|
||||
|
||||
void* platform_thread_bootstrap(void* data)
|
||||
{
|
||||
platform_thread* thread = (platform_thread*)data;
|
||||
if(strlen(thread->name))
|
||||
{
|
||||
pthread_setname_np(thread->name);
|
||||
}
|
||||
return(thread->start(thread->userPointer));
|
||||
}
|
||||
|
||||
platform_thread* ThreadCreateWithName(ThreadStartFunction start, void* userPointer, const char* name)
|
||||
{
|
||||
platform_thread* thread = (platform_thread*)malloc(sizeof(platform_thread));
|
||||
if(!thread)
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
|
||||
if(name)
|
||||
{
|
||||
char* end = stpncpy(thread->name, name, PLATFORM_THREAD_NAME_MAX_SIZE-1);
|
||||
*end = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
thread->name[0] = '\0';
|
||||
}
|
||||
thread->start = start;
|
||||
thread->userPointer = userPointer;
|
||||
|
||||
if(pthread_create(&thread->pthread, 0, platform_thread_bootstrap, thread) != 0)
|
||||
{
|
||||
free(thread);
|
||||
return(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
thread->valid = true;
|
||||
return(thread);
|
||||
}
|
||||
}
|
||||
|
||||
platform_thread* ThreadCreate(ThreadStartFunction start, void* userPointer)
|
||||
{
|
||||
return(ThreadCreateWithName(start, userPointer, 0));
|
||||
}
|
||||
|
||||
void ThreadCancel(platform_thread* thread)
|
||||
{
|
||||
pthread_cancel(thread->pthread);
|
||||
}
|
||||
|
||||
const char* ThreadGetName(platform_thread* thread)
|
||||
{
|
||||
return(thread->name);
|
||||
}
|
||||
|
||||
|
||||
u64 ThreadUniqueID(platform_thread* thread)
|
||||
{
|
||||
u64 id;
|
||||
pthread_threadid_np(thread->pthread, &id);
|
||||
return(id);
|
||||
}
|
||||
|
||||
u64 ThreadSelfID()
|
||||
{
|
||||
pthread_t thread = pthread_self();
|
||||
u64 id;
|
||||
pthread_threadid_np(thread, &id);
|
||||
return(id);
|
||||
}
|
||||
|
||||
int ThreadSignal(platform_thread* thread, int sig)
|
||||
{
|
||||
return(pthread_kill(thread->pthread, sig));
|
||||
}
|
||||
|
||||
int ThreadJoin(platform_thread* thread, void** ret)
|
||||
{
|
||||
if(pthread_join(thread->pthread, ret))
|
||||
{
|
||||
return(-1);
|
||||
}
|
||||
free(thread);
|
||||
return(0);
|
||||
}
|
||||
|
||||
int ThreadDetach(platform_thread* thread)
|
||||
{
|
||||
if(pthread_detach(thread->pthread))
|
||||
{
|
||||
return(-1);
|
||||
}
|
||||
free(thread);
|
||||
return(0);
|
||||
}
|
||||
|
||||
|
||||
struct platform_mutex
|
||||
{
|
||||
pthread_mutex_t pmutex;
|
||||
};
|
||||
|
||||
platform_mutex* MutexCreate()
|
||||
{
|
||||
platform_mutex* mutex = (platform_mutex*)malloc(sizeof(platform_mutex));
|
||||
if(!mutex)
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
if(pthread_mutex_init(&mutex->pmutex, 0) != 0)
|
||||
{
|
||||
free(mutex);
|
||||
return(0);
|
||||
}
|
||||
return(mutex);
|
||||
}
|
||||
int MutexDestroy(platform_mutex* mutex)
|
||||
{
|
||||
if(pthread_mutex_destroy(&mutex->pmutex) != 0)
|
||||
{
|
||||
return(-1);
|
||||
}
|
||||
free(mutex);
|
||||
return(0);
|
||||
}
|
||||
|
||||
int MutexLock(platform_mutex* mutex)
|
||||
{
|
||||
return(pthread_mutex_lock(&mutex->pmutex));
|
||||
}
|
||||
|
||||
int MutexUnlock(platform_mutex* mutex)
|
||||
{
|
||||
return(pthread_mutex_unlock(&mutex->pmutex));
|
||||
}
|
||||
|
||||
void TicketSpinMutexInit(ticket_spin_mutex* mutex)
|
||||
{
|
||||
mutex->nextTicket = 0;
|
||||
mutex->serving = 0;
|
||||
}
|
||||
|
||||
void TicketSpinMutexLock(ticket_spin_mutex* mutex)
|
||||
{
|
||||
u64 ticket = atomic_fetch_add(&mutex->nextTicket, 1ULL);
|
||||
while(ticket != mutex->serving); //spin
|
||||
}
|
||||
|
||||
void TicketSpinMutexUnlock(ticket_spin_mutex* mutex)
|
||||
{
|
||||
atomic_fetch_add(&mutex->serving, 1ULL);
|
||||
}
|
||||
|
||||
struct platform_condition
|
||||
{
|
||||
pthread_cond_t pcond;
|
||||
};
|
||||
|
||||
platform_condition* ConditionCreate()
|
||||
{
|
||||
platform_condition* cond = (platform_condition*)malloc(sizeof(platform_condition));
|
||||
if(!cond)
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
if(pthread_cond_init(&cond->pcond, 0) != 0)
|
||||
{
|
||||
free(cond);
|
||||
return(0);
|
||||
}
|
||||
return(cond);
|
||||
}
|
||||
int ConditionDestroy(platform_condition* cond)
|
||||
{
|
||||
if(pthread_cond_destroy(&cond->pcond) != 0)
|
||||
{
|
||||
return(-1);
|
||||
}
|
||||
free(cond);
|
||||
return(0);
|
||||
}
|
||||
int ConditionWait(platform_condition* cond, platform_mutex* mutex)
|
||||
{
|
||||
return(pthread_cond_wait(&cond->pcond, &mutex->pmutex));
|
||||
}
|
||||
|
||||
int ConditionTimedWait(platform_condition* cond, platform_mutex* mutex, f64 seconds)
|
||||
{
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, 0);
|
||||
|
||||
i64 iSeconds = (i64)seconds;
|
||||
f64 fracSeconds = seconds - (f64)iSeconds;
|
||||
|
||||
struct timespec ts;
|
||||
ts.tv_sec = tv.tv_sec + iSeconds;
|
||||
ts.tv_nsec = tv.tv_usec * 1000 + (i32)(fracSeconds*1e9);
|
||||
ts.tv_sec += ts.tv_nsec / 1000000000;
|
||||
ts.tv_nsec = ts.tv_nsec % 1000000000;
|
||||
|
||||
return(pthread_cond_timedwait(&cond->pcond, &mutex->pmutex, &ts));
|
||||
}
|
||||
|
||||
int ConditionSignal(platform_condition* cond)
|
||||
{
|
||||
return(pthread_cond_signal(&cond->pcond));
|
||||
}
|
||||
|
||||
int ConditionBroadcast(platform_condition* cond)
|
||||
{
|
||||
return(pthread_cond_broadcast(&cond->pcond));
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: unix_base_allocator.c
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 10/09/2021
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#include<sys/mman.h>
|
||||
#include"platform_base_allocator.h"
|
||||
|
||||
/*NOTE(martin):
|
||||
Linux and MacOS don't make a distinction between reserved and committed memory, contrary to Windows
|
||||
*/
|
||||
void mem_base_nop(void* context, void* ptr, u64 size) {}
|
||||
|
||||
void* mem_base_reserve_mmap(void* context, u64 size)
|
||||
{
|
||||
return(mmap(0, size, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, 0, 0));
|
||||
}
|
||||
|
||||
void mem_base_release_mmap(void* context, void* ptr, u64 size)
|
||||
{
|
||||
munmap(ptr, size);
|
||||
}
|
||||
|
||||
mem_base_allocator* mem_base_allocator_default()
|
||||
{
|
||||
static mem_base_allocator base = {};
|
||||
if(base.reserve == 0)
|
||||
{
|
||||
base.reserve = mem_base_reserve_mmap;
|
||||
base.commit = mem_base_nop;
|
||||
base.decommit = mem_base_nop;
|
||||
base.release = mem_base_release_mmap;
|
||||
}
|
||||
return(&base);
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: unix_rng.c
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 06/03/2020
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
|
||||
#include<stdio.h>
|
||||
#include<stdlib.h>
|
||||
|
||||
#include"debug_log.h"
|
||||
#include"typedefs.h"
|
||||
|
||||
#define LOG_SUBSYSTEM "Platform"
|
||||
|
||||
int RandomSeedFromDevice()
|
||||
{
|
||||
FILE* urandom = fopen("/dev/urandom", "r");
|
||||
if(!urandom)
|
||||
{
|
||||
LOG_ERROR("can't open /dev/urandom\n");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
union
|
||||
{
|
||||
u32 u;
|
||||
char buff[4];
|
||||
} seed;
|
||||
|
||||
int size = fread(seed.buff, 1, 4, urandom);
|
||||
if(size != 4)
|
||||
{
|
||||
LOG_ERROR("couldn't read from /dev/urandom\n");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
fclose(urandom);
|
||||
srandom(seed.u);
|
||||
return(0);
|
||||
}
|
||||
|
||||
u32 RandomU32()
|
||||
{
|
||||
u32 u1 = (u32)random();
|
||||
u32 u2 = (u32)random();
|
||||
return((u1<<1) | (u2 & 0x01));
|
||||
}
|
||||
|
||||
u64 RandomU64()
|
||||
{
|
||||
u64 u1 = (u64)random();
|
||||
u64 u2 = (u64)random();
|
||||
u64 u3 = (u64)random();
|
||||
return((u1<<33) | (u2<<2) | (u3 & 0x03));
|
||||
}
|
||||
|
||||
#undef LOG_SUBSYSTEM
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,212 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: ui.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 08/08/2022
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __UI_H_
|
||||
#define __UI_H_
|
||||
|
||||
#include"typedefs.h"
|
||||
#include"lists.h"
|
||||
#include"graphics.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum
|
||||
{
|
||||
UI_FLAG_CLICKABLE = (1<<0),
|
||||
UI_FLAG_SCROLLABLE = (1<<1),
|
||||
UI_FLAG_BLOCK_MOUSE = (1<<2),
|
||||
UI_FLAG_HOT_ANIMATION = (1<<3),
|
||||
UI_FLAG_ACTIVE_ANIMATION = (1<<4),
|
||||
UI_FLAG_CLIP = (1<<5),
|
||||
UI_FLAG_DRAW_BACKGROUND = (1<<6),
|
||||
UI_FLAG_DRAW_FOREGROUND = (1<<7),
|
||||
UI_FLAG_DRAW_BORDER = (1<<8),
|
||||
UI_FLAG_DRAW_TEXT = (1<<9),
|
||||
|
||||
} ui_flags;
|
||||
|
||||
typedef struct ui_key
|
||||
{
|
||||
u64 hash;
|
||||
} ui_key;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
UI_AXIS_X,
|
||||
UI_AXIS_Y,
|
||||
UI_AXIS_COUNT
|
||||
} ui_axis;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
UI_ALIGN_START,
|
||||
UI_ALIGN_END,
|
||||
UI_ALIGN_CENTER,
|
||||
} ui_align;
|
||||
|
||||
typedef struct ui_layout
|
||||
{
|
||||
ui_axis axis;
|
||||
ui_align align[UI_AXIS_COUNT];
|
||||
|
||||
} ui_layout;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
UI_SIZE_TEXT,
|
||||
UI_SIZE_PIXELS,
|
||||
UI_SIZE_CHILDREN,
|
||||
UI_SIZE_PARENT_RATIO,
|
||||
|
||||
} ui_size_kind;
|
||||
|
||||
typedef struct ui_size
|
||||
{
|
||||
ui_size_kind kind;
|
||||
f32 value;
|
||||
f32 strictness;
|
||||
} ui_size;
|
||||
|
||||
typedef enum { UI_STYLE_NORMAL,
|
||||
UI_STYLE_HOT,
|
||||
UI_STYLE_ACTIVE,
|
||||
UI_STYLE_SELECTOR_COUNT } ui_style_selector;
|
||||
|
||||
typedef struct ui_style
|
||||
{
|
||||
mg_color backgroundColor;
|
||||
mg_color foregroundColor;
|
||||
mg_color borderColor;
|
||||
mg_color textColor;
|
||||
mg_font font;
|
||||
f32 fontSize;
|
||||
f32 borderSize;
|
||||
f32 roundness;
|
||||
f32 animationTime;
|
||||
} ui_style;
|
||||
|
||||
typedef struct ui_box ui_box;
|
||||
|
||||
typedef struct ui_sig
|
||||
{
|
||||
ui_box* box;
|
||||
|
||||
vec2 mouse;
|
||||
vec2 delta;
|
||||
vec2 wheel;
|
||||
|
||||
bool pressed;
|
||||
bool released;
|
||||
bool triggered;
|
||||
bool clicked;
|
||||
bool doubleClicked;
|
||||
bool rightClicked;
|
||||
bool dragging;
|
||||
bool hovering;
|
||||
|
||||
} ui_sig;
|
||||
|
||||
struct ui_box
|
||||
{
|
||||
// hierarchy
|
||||
list_elt listElt;
|
||||
list_info children;
|
||||
ui_box* parent;
|
||||
|
||||
// keying and caching
|
||||
list_elt bucketElt;
|
||||
ui_key key;
|
||||
u64 frameCounter;
|
||||
|
||||
// builder-provided info
|
||||
ui_flags flags;
|
||||
str8 string;
|
||||
|
||||
// layout
|
||||
u32 z;
|
||||
bool floating[UI_AXIS_COUNT];
|
||||
ui_size desiredSize[UI_AXIS_COUNT];
|
||||
ui_layout layout;
|
||||
mp_rect rect;
|
||||
f32 childrenSum[2];
|
||||
|
||||
// styling
|
||||
ui_style* styles[UI_STYLE_SELECTOR_COUNT];
|
||||
ui_style computedStyle;
|
||||
ui_style_selector styleSelector;
|
||||
|
||||
// signals
|
||||
ui_sig* sig;
|
||||
|
||||
// stateful behaviour
|
||||
bool closed;
|
||||
bool parentClosed;
|
||||
bool dragging;
|
||||
bool active;
|
||||
vec2 scroll;
|
||||
|
||||
// animation data
|
||||
f32 hotTransition;
|
||||
f32 activeTransition;
|
||||
};
|
||||
|
||||
void ui_init();
|
||||
|
||||
void ui_begin_frame(u32 width, u32 height, ui_style defaultStyle);
|
||||
void ui_end_frame();
|
||||
void ui_draw(mg_canvas canvas);
|
||||
|
||||
ui_box* ui_box_make(const char* string, ui_flags flags);
|
||||
ui_box* ui_box_begin(const char* string, ui_flags flags);
|
||||
ui_box* ui_box_make_str8(str8 string, ui_flags flags);
|
||||
ui_box* ui_box_begin_str8(str8 string, ui_flags flags);
|
||||
ui_box* ui_box_end();
|
||||
#define ui_container(name, flags) defer_loop(ui_box_begin(name, flags), ui_box_end())
|
||||
|
||||
void ui_box_set_layout(ui_box* box, ui_axis axis, ui_align alignX, ui_align alignY);
|
||||
void ui_box_set_size(ui_box* box, ui_axis axis, ui_size_kind kind, f32 value, f32 strictness);
|
||||
void ui_box_set_floating(ui_box* box, ui_axis axis, f32 pos);
|
||||
|
||||
void ui_box_set_style_selector(ui_box* box, ui_style_selector selector);
|
||||
|
||||
ui_sig ui_box_sig(ui_box* box);
|
||||
|
||||
void ui_size_push(ui_axis axis, ui_size_kind kind, f32 value, f32 strictness);
|
||||
void ui_size_pop(ui_axis axis);
|
||||
void ui_style_push(ui_style_selector selector, ui_style style);
|
||||
void ui_style_pop(ui_style_selector selector);
|
||||
|
||||
// Basic helpers
|
||||
ui_sig ui_label(const char* label);
|
||||
|
||||
ui_sig ui_button(const char* label);
|
||||
ui_box* ui_scrollbar(const char* label, f32 thumbRatio, f32* scrollValue);
|
||||
|
||||
void ui_panel_begin(const char* name);
|
||||
void ui_panel_end();
|
||||
#define ui_panel(name) defer_loop(ui_panel_begin(name), ui_panel_end())
|
||||
|
||||
ui_sig ui_tooltip_begin(const char* name);
|
||||
void ui_tooltip_end();
|
||||
#define ui_tooltip(name) defer_loop(ui_tooltip_begin(name), ui_tooltip_end())
|
||||
|
||||
void ui_menu_bar_begin(const char* label);
|
||||
void ui_menu_bar_end();
|
||||
#define ui_menu_bar(name) defer_loop(ui_menu_bar_begin(name), ui_menu_bar_end())
|
||||
|
||||
void ui_menu_begin(const char* label);
|
||||
void ui_menu_end();
|
||||
#define ui_menu(name) defer_loop(ui_menu_begin(name), ui_menu_end())
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif //__UI_H_
|
|
@ -0,0 +1,123 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: debug_log.c
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 22/10/2020
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#include<stdarg.h>
|
||||
#include<string.h>
|
||||
#include"debug_log.h"
|
||||
|
||||
static const char* LOG_HEADINGS[LOG_LEVEL_COUNT] = {
|
||||
"Error",
|
||||
"Warning",
|
||||
"Message",
|
||||
"Debug"};
|
||||
|
||||
static const char* LOG_FORMATS[LOG_LEVEL_COUNT] = {
|
||||
"\e[38;5;9m\e[1m",
|
||||
"\e[38;5;13m\e[1m",
|
||||
"\e[38;5;10m\e[1m",
|
||||
"\e[38;5;14m\e[1m" };
|
||||
|
||||
static const char* LOG_FORMAT_STOP = "\e[m";
|
||||
|
||||
const int LOG_SUBSYSTEM_MAX_COUNT = 16;
|
||||
|
||||
typedef struct log_config
|
||||
{
|
||||
FILE* out;
|
||||
log_level level;
|
||||
const char* subsystemNames[LOG_SUBSYSTEM_MAX_COUNT];
|
||||
log_level subsystemLevels[LOG_SUBSYSTEM_MAX_COUNT];
|
||||
|
||||
} log_config;
|
||||
|
||||
static log_config __log_config = {.out = 0,
|
||||
.level = LOG_DEFAULT_LEVEL,
|
||||
.subsystemNames = {},
|
||||
.subsystemLevels = {}};
|
||||
|
||||
int LogFindSubsystem(const char* subsystem)
|
||||
{
|
||||
for(int i=0; i<LOG_SUBSYSTEM_MAX_COUNT; i++)
|
||||
{
|
||||
if(__log_config.subsystemNames[i])
|
||||
{
|
||||
if(!strcmp(__log_config.subsystemNames[i], subsystem))
|
||||
{
|
||||
return(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return(-1);
|
||||
}
|
||||
|
||||
void LogGeneric(log_level level,
|
||||
const char* subsystem,
|
||||
const char* functionName,
|
||||
const char* fileName,
|
||||
u32 line,
|
||||
const char* msg,
|
||||
...)
|
||||
{
|
||||
int subsystemIndex = LogFindSubsystem(subsystem);
|
||||
int filterLevel = (subsystemIndex >= 0)? __log_config.subsystemLevels[subsystemIndex] : __log_config.level;
|
||||
|
||||
if(level <= filterLevel)
|
||||
{
|
||||
if(!__log_config.out)
|
||||
{
|
||||
__log_config.out = LOG_DEFAULT_OUTPUT;
|
||||
}
|
||||
fprintf(__log_config.out,
|
||||
"%s%s:%s [%s] %s() in %s:%i: ",
|
||||
LOG_FORMATS[level],
|
||||
LOG_HEADINGS[level],
|
||||
LOG_FORMAT_STOP,
|
||||
subsystem,
|
||||
functionName,
|
||||
fileName,
|
||||
line);
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
vfprintf(__log_config.out, msg, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
|
||||
void LogOutput(FILE* output)
|
||||
{
|
||||
__log_config.out = output;
|
||||
}
|
||||
|
||||
void LogLevel(log_level level)
|
||||
{
|
||||
__log_config.level = level;
|
||||
}
|
||||
|
||||
void LogFilter(const char* subsystem, log_level level)
|
||||
{
|
||||
int firstNull = -1;
|
||||
for(int i=0; i<LOG_SUBSYSTEM_MAX_COUNT; i++)
|
||||
{
|
||||
if(__log_config.subsystemNames[i])
|
||||
{
|
||||
if(!strcmp(__log_config.subsystemNames[i], subsystem))
|
||||
{
|
||||
__log_config.subsystemLevels[i] = level;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if(firstNull < 0)
|
||||
{
|
||||
firstNull = i;
|
||||
}
|
||||
}
|
||||
|
||||
__log_config.subsystemNames[firstNull] = subsystem;
|
||||
__log_config.subsystemLevels[firstNull] = level;
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: debug_log.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 05/04/2019
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __DEBUG_LOG_H_
|
||||
#define __DEBUG_LOG_H_
|
||||
|
||||
#include<stdio.h>
|
||||
#include"typedefs.h"
|
||||
#include"macro_helpers.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
//NOTE(martin): the default logging level can be adjusted by defining LOG_DEFAULT_LEVEL. As the name suggest, it is the default, but it
|
||||
// can be adjusted at runtime with LogLevel()
|
||||
#ifndef LOG_DEFAULT_LEVEL
|
||||
#define LOG_DEFAULT_LEVEL LOG_LEVEL_WARNING
|
||||
#endif
|
||||
|
||||
//NOTE(martin): the default output can be adjusted by defining LOG_DEFAULT_OUTPUT. It can be adjusted at runtime with LogOutput()
|
||||
#ifndef LOG_DEFAULT_OUTPUT
|
||||
#define LOG_DEFAULT_OUTPUT stdout
|
||||
#endif
|
||||
|
||||
//NOTE(martin): LOG_SUBSYSTEM can be defined in each compilation unit to associate it with a subsystem, like this:
|
||||
// #define LOG_SUBSYSTEM "name"
|
||||
|
||||
typedef enum { LOG_LEVEL_ERROR,
|
||||
LOG_LEVEL_WARNING,
|
||||
LOG_LEVEL_MESSAGE,
|
||||
LOG_LEVEL_DEBUG,
|
||||
LOG_LEVEL_COUNT } log_level;
|
||||
|
||||
void LogGeneric(log_level level,
|
||||
const char* subsystem,
|
||||
const char* functionName,
|
||||
const char* fileName,
|
||||
u32 line,
|
||||
const char* msg,
|
||||
...);
|
||||
|
||||
void LogOutput(FILE* output);
|
||||
void LogLevel(log_level level);
|
||||
void LogFilter(const char* subsystem, log_level level);
|
||||
|
||||
#define LOG_GENERIC(level, func, file, line, msg, ...) LogGeneric(level, LOG_SUBSYSTEM, func, file, line, msg, ##__VA_ARGS__ )
|
||||
|
||||
#define LOG_ERROR(msg, ...) LOG_GENERIC(LOG_LEVEL_ERROR, __FUNCTION__, __FILE__, __LINE__, msg, ##__VA_ARGS__ )
|
||||
|
||||
//NOTE(martin): warnings, messages, and debug info can be enabled in debug mode by defining LOG_COMPILE_XXX, XXX being the max desired log level
|
||||
// error logging is always compiled
|
||||
#if defined(LOG_COMPILE_WARNING) || defined(LOG_COMPILE_MESSAGE) || defined(LOG_COMPILE_DEBUG)
|
||||
#define LOG_WARNING(msg, ...) LOG_GENERIC(LOG_LEVEL_WARNING, __FUNCTION__, __FILE__, __LINE__, msg, ##__VA_ARGS__ )
|
||||
|
||||
#if defined(LOG_COMPILE_MESSAGE) || defined(LOG_COMPILE_DEBUG)
|
||||
#define LOG_MESSAGE(msg, ...) LOG_GENERIC(LOG_LEVEL_MESSAGE, __FUNCTION__, __FILE__, __LINE__, msg, ##__VA_ARGS__ )
|
||||
|
||||
#if defined(LOG_COMPILE_DEBUG)
|
||||
#define LOG_DEBUG(msg, ...) LOG_GENERIC(LOG_LEVEL_DEBUG, __FUNCTION__, __FILE__, __LINE__, msg, ##__VA_ARGS__ )
|
||||
#else
|
||||
#define LOG_DEBUG(msg, ...)
|
||||
#endif
|
||||
#else
|
||||
#define LOG_MESSAGE(msg, ...)
|
||||
#define LOG_DEBUG(msg, ...)
|
||||
#endif
|
||||
#else
|
||||
#define LOG_WARNING(msg, ...)
|
||||
#define LOG_MESSAGE(msg, ...)
|
||||
#define LOG_DEBUG(msg, ...)
|
||||
#endif
|
||||
|
||||
#ifndef NO_ASSERT
|
||||
#include<assert.h>
|
||||
#define _ASSERT_(x, msg) assert(x && msg)
|
||||
#define ASSERT(x, ...) _ASSERT_(x, #__VA_ARGS__)
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DEBUG_ASSERT(x, ...) ASSERT(x, ##__VA_ARGS__ )
|
||||
#else
|
||||
#define DEBUG_ASSERT(x, ...)
|
||||
#endif
|
||||
#else
|
||||
#define ASSERT(x, ...)
|
||||
#define DEBUG_ASSERT(x, ...)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif //__DEBUG_LOG_H_
|
|
@ -0,0 +1,124 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: hash.cpp
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 08/08/2022
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#include<immintrin.h>
|
||||
#include"hash.h"
|
||||
|
||||
u64 mp_hash_aes_u64(u64 x)
|
||||
{
|
||||
u8 seed[16] = {
|
||||
0xaa, 0x9b, 0xbd, 0xb8,
|
||||
0xa1, 0x98, 0xac, 0x3f,
|
||||
0x1f, 0x94, 0x07, 0xb3,
|
||||
0x8c, 0x27, 0x93, 0x69 };
|
||||
|
||||
__m128i hash = _mm_set_epi64x(0L, x);
|
||||
__m128i key = _mm_loadu_si128((__m128i*)seed);
|
||||
hash = _mm_aesdec_si128(hash, key);
|
||||
hash = _mm_aesdec_si128(hash, key);
|
||||
u64 result = _mm_extract_epi64(hash, 0);
|
||||
|
||||
return(result);
|
||||
}
|
||||
|
||||
u64 mp_hash_aes_u64_x2(u64 x, u64 y)
|
||||
{
|
||||
u8 seed[16] = {
|
||||
0xaa, 0x9b, 0xbd, 0xb8,
|
||||
0xa1, 0x98, 0xac, 0x3f,
|
||||
0x1f, 0x94, 0x07, 0xb3,
|
||||
0x8c, 0x27, 0x93, 0x69 };
|
||||
|
||||
__m128i hash = _mm_set_epi64x(x, y);
|
||||
__m128i key = _mm_loadu_si128((__m128i*)seed);
|
||||
hash = _mm_aesdec_si128(hash, key);
|
||||
hash = _mm_aesdec_si128(hash, key);
|
||||
u64 result = _mm_extract_epi64(hash, 0);
|
||||
|
||||
return(result);
|
||||
}
|
||||
|
||||
u64 mp_hash_aes_string(str8 string)
|
||||
{
|
||||
u8 seed[16] = {
|
||||
0xaa, 0x9b, 0xbd, 0xb8,
|
||||
0xa1, 0x98, 0xac, 0x3f,
|
||||
0x1f, 0x94, 0x07, 0xb3,
|
||||
0x8c, 0x27, 0x93, 0x69 };
|
||||
|
||||
__m128i hash = _mm_loadu_si128((__m128i*)seed);
|
||||
|
||||
u64 chunkCount = string.len / 16;
|
||||
char* at = string.ptr;
|
||||
|
||||
while(chunkCount--)
|
||||
{
|
||||
__m128i in = _mm_loadu_si128((__m128i*)at);
|
||||
at += 16;
|
||||
|
||||
hash = _mm_xor_si128(hash, in);
|
||||
hash = _mm_aesdec_si128(hash, _mm_setzero_si128());
|
||||
hash = _mm_aesdec_si128(hash, _mm_setzero_si128());
|
||||
hash = _mm_aesdec_si128(hash, _mm_setzero_si128());
|
||||
hash = _mm_aesdec_si128(hash, _mm_setzero_si128());
|
||||
}
|
||||
|
||||
u64 restCount = string.len % 16;
|
||||
char tmp[16];
|
||||
memset(tmp, 0, 16);
|
||||
memmove(tmp, at, restCount);
|
||||
|
||||
__m128i in = _mm_loadu_si128((__m128i*)tmp);
|
||||
hash = _mm_xor_si128(hash, in);
|
||||
hash = _mm_aesdec_si128(hash, _mm_setzero_si128());
|
||||
hash = _mm_aesdec_si128(hash, _mm_setzero_si128());
|
||||
hash = _mm_aesdec_si128(hash, _mm_setzero_si128());
|
||||
hash = _mm_aesdec_si128(hash, _mm_setzero_si128());
|
||||
|
||||
u64 result = _mm_extract_epi64(hash, 0);
|
||||
return(result);
|
||||
}
|
||||
|
||||
u64 mp_hash_aes_string_seed(str8 string, u64 seed)
|
||||
{
|
||||
u8 seed16[16];
|
||||
memcpy(seed16, &seed, 8);
|
||||
memcpy(seed16+8, &seed, 8);
|
||||
|
||||
__m128i hash = _mm_loadu_si64(&seed16);
|
||||
|
||||
u64 chunkCount = string.len / 16;
|
||||
char* at = string.ptr;
|
||||
|
||||
while(chunkCount--)
|
||||
{
|
||||
__m128i in = _mm_loadu_si128((__m128i*)at);
|
||||
at += 16;
|
||||
|
||||
hash = _mm_xor_si128(hash, in);
|
||||
hash = _mm_aesdec_si128(hash, _mm_setzero_si128());
|
||||
hash = _mm_aesdec_si128(hash, _mm_setzero_si128());
|
||||
hash = _mm_aesdec_si128(hash, _mm_setzero_si128());
|
||||
hash = _mm_aesdec_si128(hash, _mm_setzero_si128());
|
||||
}
|
||||
|
||||
u64 restCount = string.len % 16;
|
||||
char tmp[16];
|
||||
memset(tmp, 0, 16);
|
||||
memmove(tmp, at, restCount);
|
||||
|
||||
__m128i in = _mm_loadu_si128((__m128i*)tmp);
|
||||
hash = _mm_xor_si128(hash, in);
|
||||
hash = _mm_aesdec_si128(hash, _mm_setzero_si128());
|
||||
hash = _mm_aesdec_si128(hash, _mm_setzero_si128());
|
||||
hash = _mm_aesdec_si128(hash, _mm_setzero_si128());
|
||||
hash = _mm_aesdec_si128(hash, _mm_setzero_si128());
|
||||
|
||||
u64 result = _mm_extract_epi64(hash, 0);
|
||||
return(result);
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: hash.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 08/08/2022
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __HASH_H_
|
||||
#define __HASH_H_
|
||||
|
||||
#include"typedefs.h"
|
||||
#include"strings.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
u64 mp_hash_aes_u64(u64 x);
|
||||
u64 mp_hash_aes_u64_x2(u64 x, u64 y);
|
||||
u64 mp_hash_aes_string(str8 string);
|
||||
u64 mp_hash_aes_string_seed(str8 string, u64 seed);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
|
||||
#endif //__HASH_H_
|
|
@ -0,0 +1,390 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: lists.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 22/11/2017
|
||||
* @revision: 28/04/2019 : deleted containers which are not used by BLITz
|
||||
* @brief: Implements generic intrusive linked list and dynamic array
|
||||
*
|
||||
****************************************************************/
|
||||
#ifndef __CONTAINERS_H_
|
||||
#define __CONTAINERS_H_
|
||||
|
||||
#include"debug_log.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define OFFSET_OF_CONTAINER(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define CONTAINER_OF(ptr, type, member) ({ \
|
||||
const decltype( ((type *)0)->member ) *__mptr = (ptr); \
|
||||
(type *)( (char *)__mptr - OFFSET_OF_CONTAINER(type,member) );})
|
||||
#else
|
||||
#define CONTAINER_OF(ptr, type, member) ({ \
|
||||
const char *__mptr = (char*)(ptr); \
|
||||
(type *)(__mptr - OFFSET_OF_CONTAINER(type,member) );})
|
||||
#endif
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Intrusive linked lists
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
#define ListEntry(ptr, type, member) \
|
||||
CONTAINER_OF(ptr, type, member)
|
||||
|
||||
#define ListNext(elt) (elt)->next
|
||||
#define ListPrev(elt) (elt)->prev
|
||||
|
||||
#define ListNextEntry(list, elt, type, member) \
|
||||
((elt->member.next != ListEnd(list)) ? ListEntry(elt->member.next, type, member) : 0)
|
||||
|
||||
#define ListPrevEntry(list, elt, type, member) \
|
||||
((elt->member.prev != ListEnd(list)) ? ListEntry(elt->member.prev, type, member) : 0)
|
||||
|
||||
#define ListCheckedEntry(list, type, member) \
|
||||
(((list) != 0) ? ListEntry(list, type, member) : 0)
|
||||
|
||||
#define ListFirstEntry(list, type, member) \
|
||||
(ListCheckedEntry(ListBegin(list), type, member))
|
||||
|
||||
#define ListLastEntry(list, type, member) \
|
||||
(ListCheckedEntry(ListLast(list), type, member))
|
||||
|
||||
#define for_each_in_list(list, elt, type, member) \
|
||||
for(type* elt = ListCheckedEntry(ListBegin(list), type, member); \
|
||||
elt != 0; \
|
||||
elt = ListCheckedEntry(elt->member.next, type, member)) \
|
||||
|
||||
#define for_each_in_list_reverse(list, elt, type, member) \
|
||||
for(type* elt = ListCheckedEntry(ListLast(list), type, member); \
|
||||
elt != 0; \
|
||||
elt = ListCheckedEntry(elt->member.prev, type, member)) \
|
||||
|
||||
#define for_each_in_list_safe(list, elt, type, member) \
|
||||
for(type* elt = ListCheckedEntry(ListBegin(list), type, member), \
|
||||
*__tmp = elt ? ListCheckedEntry(elt->member.next, type, member) : 0 ; \
|
||||
elt != 0; \
|
||||
elt = __tmp, \
|
||||
__tmp = elt ? ListCheckedEntry(elt->member.next, type, member) : 0) \
|
||||
|
||||
#define ListPopEntry(list, type, member) (ListEmpty(list) ? 0 : ListEntry(ListPop(list), type, member))
|
||||
|
||||
typedef struct list_elt list_elt;
|
||||
struct list_elt
|
||||
{
|
||||
list_elt* next;
|
||||
list_elt* prev;
|
||||
};
|
||||
|
||||
typedef struct list_info
|
||||
{
|
||||
list_elt* first;
|
||||
list_elt* last;
|
||||
} list_info;
|
||||
|
||||
static inline void ListInit(list_info* list)
|
||||
{
|
||||
list->first = list->last = 0;
|
||||
}
|
||||
|
||||
static inline list_elt* ListBegin(list_info* list)
|
||||
{
|
||||
return(list->first);
|
||||
}
|
||||
static inline list_elt* ListEnd(list_info* list)
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
|
||||
static inline list_elt* ListLast(list_info* list)
|
||||
{
|
||||
return(list->last);
|
||||
}
|
||||
|
||||
static inline void ListInsert(list_info* list, list_elt* afterElt, list_elt* elt)
|
||||
{
|
||||
elt->prev = afterElt;
|
||||
elt->next = afterElt->next;
|
||||
if(afterElt->next)
|
||||
{
|
||||
afterElt->next->prev = elt;
|
||||
}
|
||||
else
|
||||
{
|
||||
list->last = elt;
|
||||
}
|
||||
afterElt->next = elt;
|
||||
|
||||
ASSERT(elt->next != elt, "ListInsert(): can't insert an element into itself");
|
||||
}
|
||||
|
||||
static inline void ListInsertBefore(list_info* list, list_elt* beforeElt, list_elt* elt)
|
||||
{
|
||||
elt->next = beforeElt;
|
||||
elt->prev = beforeElt->prev;
|
||||
|
||||
if(beforeElt->prev)
|
||||
{
|
||||
beforeElt->prev->next = elt;
|
||||
}
|
||||
else
|
||||
{
|
||||
list->first = elt;
|
||||
}
|
||||
beforeElt->prev = elt;
|
||||
|
||||
ASSERT(elt->next != elt, "ListInsertBefore(): can't insert an element into itself");
|
||||
}
|
||||
|
||||
static inline void ListRemove(list_info* list, list_elt* elt)
|
||||
{
|
||||
if(elt->prev)
|
||||
{
|
||||
elt->prev->next = elt->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ASSERT(list->first == elt);
|
||||
list->first = elt->next;
|
||||
}
|
||||
if(elt->next)
|
||||
{
|
||||
elt->next->prev = elt->prev;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ASSERT(list->last == elt);
|
||||
list->last = elt->prev;
|
||||
}
|
||||
elt->prev = elt->next = 0;
|
||||
}
|
||||
|
||||
static inline void ListPush(list_info* list, list_elt* elt)
|
||||
{
|
||||
elt->next = list->first;
|
||||
elt->prev = 0;
|
||||
if(list->first)
|
||||
{
|
||||
list->first->prev = elt;
|
||||
}
|
||||
else
|
||||
{
|
||||
list->last = elt;
|
||||
}
|
||||
list->first = elt;
|
||||
}
|
||||
|
||||
static inline list_elt* ListPop(list_info* list)
|
||||
{
|
||||
list_elt* elt = ListBegin(list);
|
||||
if(elt != ListEnd(list))
|
||||
{
|
||||
ListRemove(list, elt);
|
||||
return(elt);
|
||||
}
|
||||
else
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ListPushBack(list_info* list, list_elt* elt)
|
||||
{
|
||||
elt->prev = list->last;
|
||||
elt->next = 0;
|
||||
if(list->last)
|
||||
{
|
||||
list->last->next = elt;
|
||||
}
|
||||
else
|
||||
{
|
||||
list->first = elt;
|
||||
}
|
||||
list->last = elt;
|
||||
}
|
||||
#define ListAppend(a, b) ListPushBack(a, b)
|
||||
|
||||
|
||||
static inline list_elt* ListPopBack(list_info* list)
|
||||
{
|
||||
list_elt* elt = ListLast(list);
|
||||
if(elt != ListEnd(list))
|
||||
{
|
||||
ListRemove(list, elt);
|
||||
return(elt);
|
||||
}
|
||||
else
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool ListEmpty(list_info* list)
|
||||
{
|
||||
return(list->first == 0 || list->last == 0);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Circular Intrusive linked lists
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
#define CListEntry(ptr, type, member) ListEntry(ptr, type, member)
|
||||
#define CListNext(elt) ListNext(elt)
|
||||
#define CListPrev(elt) ListPrev(elt)
|
||||
|
||||
#define CListNextEntry(head, elt, type, member) \
|
||||
((elt->member.next != CListEnd(head)) ? CListEntry(elt->member.next, type, member) : 0)
|
||||
|
||||
#define CListPrevEntry(head, elt, type, member) \
|
||||
((elt->member.prev != CListEnd(head)) ? CListEntry(elt->member.prev, type, member) : 0)
|
||||
|
||||
#define CListCheckedEntry(head, info, type, member) \
|
||||
((info != CListEnd(head)) ? CListEntry(info, type, member) : 0)
|
||||
|
||||
#define CListFirstEntry(head, type, member) \
|
||||
(CListCheckedEntry(head, CListBegin(head), type, member))
|
||||
|
||||
#define CListLastEntry(head, type, member) \
|
||||
(CListCheckedEntry(head, CListLast(head), type, member))
|
||||
|
||||
#define for_each_in_clist(list, elt, type, member) \
|
||||
for(type* elt = CListEntry(CListBegin(list), type, member); \
|
||||
&elt->member != CListEnd(list); \
|
||||
elt = CListEntry(elt->member.next, type, member)) \
|
||||
|
||||
|
||||
#define for_each_in_clist_reverse(list, elt, type, member) \
|
||||
for(type* elt = CListEntry(CListLast(list), type, member); \
|
||||
&elt->member != CListEnd(list); \
|
||||
elt = CListEntry(elt->member.prev, type, member)) \
|
||||
|
||||
|
||||
#define for_each_in_clist_safe(list, elt, type, member) \
|
||||
for(type* elt = CListEntry(CListBegin(list), type, member), \
|
||||
*__tmp = CListEntry(elt->member.next, type, member); \
|
||||
&elt->member != CListEnd(list); \
|
||||
elt = CListEntry(&__tmp->member, type, member), \
|
||||
__tmp = CListEntry(elt->member.next, type, member)) \
|
||||
|
||||
|
||||
#define CListPush(a, b) CListInsert(a, b)
|
||||
#define CListInsertBefore(a, b) CListAppend(a, b)
|
||||
|
||||
#define CListPopEntry(list, type, member) (CListEmpty(list) ? 0 : CListEntry(CListPop(list), type, member))
|
||||
|
||||
static inline void CListInit(list_elt* info)
|
||||
{
|
||||
info->next = info->prev = info;
|
||||
}
|
||||
|
||||
static inline list_elt* CListBegin(list_elt* head)
|
||||
{
|
||||
return(head->next ? head->next : head );
|
||||
}
|
||||
static inline list_elt* CListEnd(list_elt* head)
|
||||
{
|
||||
return(head);
|
||||
}
|
||||
|
||||
static inline list_elt* CListLast(list_elt* head)
|
||||
{
|
||||
return(head->prev ? head->prev : head);
|
||||
}
|
||||
|
||||
static inline void CListInsert(list_elt* head, list_elt* elt)
|
||||
{
|
||||
elt->prev = head;
|
||||
elt->next = head->next;
|
||||
if(head->next)
|
||||
{
|
||||
head->next->prev = elt;
|
||||
}
|
||||
else
|
||||
{
|
||||
head->prev = elt;
|
||||
}
|
||||
head->next = elt;
|
||||
|
||||
ASSERT(elt->next != elt, "CListInsert(): can't insert an element into itself");
|
||||
}
|
||||
|
||||
static inline void CListAppend(list_elt* head, list_elt* elt)
|
||||
{
|
||||
CListInsert(head->prev, elt);
|
||||
}
|
||||
|
||||
static inline void CListCat(list_elt* head, list_elt* list)
|
||||
{
|
||||
if(head->prev)
|
||||
{
|
||||
head->prev->next = list->next;
|
||||
}
|
||||
if(head->prev && head->prev->next)
|
||||
{
|
||||
head->prev->next->prev = head->prev;
|
||||
}
|
||||
head->prev = list->prev;
|
||||
if(head->prev)
|
||||
{
|
||||
head->prev->next = head;
|
||||
}
|
||||
CListInit(list);
|
||||
}
|
||||
|
||||
static inline void CListRemove(list_elt* elt)
|
||||
{
|
||||
if(elt->prev)
|
||||
{
|
||||
elt->prev->next = elt->next;
|
||||
}
|
||||
if(elt->next)
|
||||
{
|
||||
elt->next->prev = elt->prev;
|
||||
}
|
||||
elt->prev = elt->next = 0;
|
||||
}
|
||||
|
||||
static inline list_elt* CListPop(list_elt* head)
|
||||
{
|
||||
list_elt* it = CListBegin(head);
|
||||
if(it != CListEnd(head))
|
||||
{
|
||||
CListRemove(it);
|
||||
return(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static inline list_elt* CListPopBack(list_elt* head)
|
||||
{
|
||||
list_elt* it = CListLast(head);
|
||||
if(it != CListEnd(head))
|
||||
{
|
||||
CListRemove(it);
|
||||
return(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool CListEmpty(list_elt* head)
|
||||
{
|
||||
return(head->next == 0 || head->next == head);
|
||||
}
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif //__CONTAINERS_H_
|
|
@ -0,0 +1,129 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: macro_helpers.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 27/03/2020
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __MACRO_HELPERS_H_
|
||||
#define __MACRO_HELPERS_H_
|
||||
|
||||
//NOTE(martin): macro concatenation
|
||||
#define _cat2_(a, b) a##b
|
||||
#define _cat3_(a, b, c) a##b##c
|
||||
|
||||
|
||||
//NOTE(martin): inline, but still generate code
|
||||
// (eg. use the inline version inside a library, but still exports the function for client code)
|
||||
//TODO(martin): this is a compiler-specific attribute, recognized by clang and gcc. See if there's a more portable approach
|
||||
//#define INLINE_GEN __attribute__((used)) static inline
|
||||
|
||||
|
||||
//NOTE(martin): typed and array mallocs
|
||||
#define malloc_type(type) ((type*)malloc(sizeof(type)))
|
||||
#define malloc_array(type, count) ((type*)malloc(sizeof(type)*count))
|
||||
|
||||
//NOTE(martin): 'hygienic' templates, to replace macros and avoid multiple evaluation problems.
|
||||
#ifdef __cplusplus
|
||||
//NOTE(martin): in C++ we use templates and decltype/declval
|
||||
// (overloaded functions would be ambiguous because of the
|
||||
// overload resolution and conversion/promotion rules)
|
||||
|
||||
#include<utility>
|
||||
|
||||
template<typename Ta, typename Tb>
|
||||
inline decltype(std::declval<Ta>()+std::declval<Tb>()) minimum_safe(Ta a, Tb b)
|
||||
{
|
||||
return(a < b ? a : b);
|
||||
}
|
||||
|
||||
template<typename Ta, typename Tb>
|
||||
inline decltype(std::declval<Ta>()+std::declval<Tb>()) maximum_safe(Ta a, Tb b)
|
||||
{
|
||||
return(a > b ? a : b);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline T square_safe(T a) {return(a*a);}
|
||||
|
||||
template<typename T>
|
||||
inline T cube_safe(T a) {return(a*a*a);}
|
||||
|
||||
#else // (__cplusplus not defined)
|
||||
|
||||
//NOTE(martin): Type generic arithmetic functions helpers
|
||||
// this macros helps generate variants of a generic 'template' for all arithmetic types.
|
||||
// the def parameter must be a macro that take a type, and optional arguments
|
||||
#define tga_generate_variants(def, ...) \
|
||||
def(u8, ##__VA_ARGS__) def(i8, ##__VA_ARGS__ ) def(u16, ##__VA_ARGS__) def(i16, ##__VA_ARGS__) \
|
||||
def(u32, ##__VA_ARGS__) def(i32, ##__VA_ARGS__) def(u64, ##__VA_ARGS__) def(i64, ##__VA_ARGS__) \
|
||||
def(f32, ##__VA_ARGS__) def(f64, ##__VA_ARGS__)
|
||||
|
||||
// This macro generates the name of a typed variant
|
||||
#define tga_variant_name(name, type) _cat3_(name, _, type)
|
||||
|
||||
// This macro generates a _Generic association between a type and its variant
|
||||
#define tga_variant_association(type, name) , type: tga_variant_name(name, type)
|
||||
|
||||
// This macros selects the appropriate variant for a 2 parameters functions
|
||||
#define tga_select_binary(name, a, b) \
|
||||
_Generic((a+b) tga_generate_variants(tga_variant_association, name))(a, b)
|
||||
|
||||
// This macros selects the appropriate variant for a 1 parameters functions
|
||||
#define tga_select_unary(name, a) \
|
||||
_Generic((a) tga_generate_variants(tga_variant_association, name))(a)
|
||||
|
||||
//NOTE(martin): type generic templates
|
||||
#define minimum_def(type) static inline type tga_variant_name(minimum_safe, type)(type a, type b) {return(a < b ? a : b);}
|
||||
#define maximum_def(type) static inline type tga_variant_name(maximum_safe, type)(type a, type b) {return(a > b ? a : b);}
|
||||
#define square_def(type) static inline type tga_variant_name(square_safe, type)(type a) {return(a*a);}
|
||||
#define cube_def(type) static inline type tga_variant_name(cube_safe, type)(type a) {return(a*a*a);}
|
||||
|
||||
//NOTE(martin): instantiante our templates for all arithmetic types
|
||||
tga_generate_variants(minimum_def)
|
||||
tga_generate_variants(maximum_def)
|
||||
tga_generate_variants(square_def)
|
||||
tga_generate_variants(cube_def)
|
||||
|
||||
//NOTE(martin): select the correct variant according to the argument types
|
||||
#define minimum_safe(a, b) tga_select_binary(minimum_safe, a, b)
|
||||
#define maximum_safe(a, b) tga_select_binary(maximum_safe, a, b)
|
||||
#define square_safe(a) tga_select_unary(square_safe, a)
|
||||
#define cube_safe(a) tga_select_unary(cube_safe, a)
|
||||
|
||||
#endif // __cplusplus else branch
|
||||
|
||||
|
||||
//NOTE(martin): these macros are calling the safe functions defined above, so they don't evaluate their
|
||||
// arguments twice
|
||||
|
||||
#define minimum(a, b) minimum_safe(a, b)
|
||||
#define maximum(a, b) maximum_safe(a, b)
|
||||
|
||||
#define ClampLowBound(a, low) (maximum((a), (low)))
|
||||
#define ClampHighBound(a, high) (minimum((a), (high)))
|
||||
#define Clamp(a, low, high) (ClampLowBound(ClampHighBound((a), (high)), (low)))
|
||||
|
||||
#define Square(a) square_safe(a)
|
||||
#define Cube(a) cube_safe(a)
|
||||
|
||||
#define AlignUpOnPow2(x, a) (((x) + (a) - 1) & ~((a)-1))
|
||||
#define AlignDownOnPow2(x, a) ((x) & ~((a)-1))
|
||||
|
||||
static inline u64 next_pow2_u64(u64 x)
|
||||
{
|
||||
x--;
|
||||
x |= x>>1;
|
||||
x |= x>>2;
|
||||
x |= x>>4;
|
||||
x |= x>>8;
|
||||
x |= x>>16;
|
||||
x |= x>>32;
|
||||
x++;
|
||||
return(x);
|
||||
}
|
||||
|
||||
#define defer_loop(begin, end) begin; for(int __i__=0; __i__<1; __i__++, end)
|
||||
|
||||
#endif //__MACRO_HELPERS_H_
|
|
@ -0,0 +1,129 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: memory.c
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 24/10/2019
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#include<string.h> // memset
|
||||
|
||||
#include"memory.h"
|
||||
#include"platform_base_allocator.h"
|
||||
#include"macro_helpers.h"
|
||||
|
||||
static const u32 MEM_ARENA_DEFAULT_RESERVE_SIZE = 1<<30;
|
||||
static const u32 MEM_ARENA_COMMIT_ALIGNMENT = 1<<20;
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//NOTE(martin): memory arena
|
||||
//--------------------------------------------------------------------------------
|
||||
void mem_arena_init(mem_arena* arena)
|
||||
{
|
||||
mem_arena_init_with_options(arena, &(mem_arena_options){});
|
||||
}
|
||||
|
||||
void mem_arena_init_with_options(mem_arena* arena, mem_arena_options* options)
|
||||
{
|
||||
arena->base = options->base ? options->base : mem_base_allocator_default();
|
||||
arena->cap = options->reserve ? options->reserve : MEM_ARENA_DEFAULT_RESERVE_SIZE;
|
||||
|
||||
arena->ptr = mem_base_reserve(arena->base, arena->cap);
|
||||
arena->committed = 0;
|
||||
arena->offset = 0;
|
||||
}
|
||||
|
||||
void mem_arena_release(mem_arena* arena)
|
||||
{
|
||||
mem_base_release(arena->base, arena->ptr, arena->cap);
|
||||
memset(arena, 0, sizeof(mem_arena));
|
||||
}
|
||||
|
||||
void* mem_arena_alloc(mem_arena* arena, u64 size)
|
||||
{
|
||||
u64 nextOffset = arena->offset + size;
|
||||
ASSERT(nextOffset <= arena->cap);
|
||||
|
||||
if(nextOffset > arena->committed)
|
||||
{
|
||||
u64 nextCommitted = AlignUpOnPow2(nextOffset, MEM_ARENA_COMMIT_ALIGNMENT);
|
||||
nextCommitted = ClampHighBound(nextCommitted, arena->cap);
|
||||
u64 commitSize = nextCommitted - arena->committed;
|
||||
mem_base_commit(arena->base, arena->ptr + arena->committed, commitSize);
|
||||
arena->committed = nextCommitted;
|
||||
}
|
||||
char* p = arena->ptr + arena->offset;
|
||||
arena->offset += size;
|
||||
|
||||
return(p);
|
||||
}
|
||||
|
||||
void mem_arena_clear(mem_arena* arena)
|
||||
{
|
||||
arena->offset = 0;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//NOTE(martin): memory pool
|
||||
//--------------------------------------------------------------------------------
|
||||
void mem_pool_init(mem_pool* pool, u64 blockSize)
|
||||
{
|
||||
mem_pool_init_with_options(pool, blockSize, &(mem_pool_options){});
|
||||
}
|
||||
void mem_pool_init_with_options(mem_pool* pool, u64 blockSize, mem_pool_options* options)
|
||||
{
|
||||
mem_arena_init_with_options(&pool->arena, &(mem_arena_options){.base = options->base, .reserve = options->reserve});
|
||||
pool->blockSize = ClampLowBound(blockSize, sizeof(list_info));
|
||||
ListInit(&pool->freeList);
|
||||
}
|
||||
|
||||
void mem_pool_release(mem_pool* pool)
|
||||
{
|
||||
mem_arena_release(&pool->arena);
|
||||
memset(pool, 0, sizeof(mem_pool));
|
||||
}
|
||||
|
||||
void* mem_pool_alloc(mem_pool* pool)
|
||||
{
|
||||
if(ListEmpty(&pool->freeList))
|
||||
{
|
||||
return(mem_arena_alloc(&pool->arena, pool->blockSize));
|
||||
}
|
||||
else
|
||||
{
|
||||
return(ListPop(&pool->freeList));
|
||||
}
|
||||
}
|
||||
|
||||
void mem_pool_recycle(mem_pool* pool, void* ptr)
|
||||
{
|
||||
ASSERT((((char*)ptr) >= pool->arena.ptr) && (((char*)ptr) < (pool->arena.ptr + pool->arena.offset)));
|
||||
ListPush(&pool->freeList, (list_elt*)ptr);
|
||||
}
|
||||
|
||||
void mem_pool_clear(mem_pool* pool)
|
||||
{
|
||||
mem_arena_clear(&pool->arena);
|
||||
ListInit(&pool->freeList);
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//NOTE(martin): per-thread scratch arena
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
__thread mem_arena __scratchArena = {};
|
||||
|
||||
mem_arena* mem_scratch()
|
||||
{
|
||||
if(__scratchArena.ptr == 0)
|
||||
{
|
||||
mem_arena_init(&__scratchArena);
|
||||
}
|
||||
return(&__scratchArena);
|
||||
}
|
||||
|
||||
void mem_scratch_clear()
|
||||
{
|
||||
mem_arena_clear(mem_scratch());
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: memory.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 24/10/2019
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __MEMORY_H_
|
||||
#define __MEMORY_H_
|
||||
|
||||
#include"typedefs.h"
|
||||
#include"lists.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//NOTE(martin): base allocator
|
||||
//--------------------------------------------------------------------------------
|
||||
typedef void*(*mem_reserve_function)(void* context, u64 size);
|
||||
typedef void(*mem_modify_function)(void* context, void* ptr, u64 size);
|
||||
|
||||
typedef struct mem_base_allocator
|
||||
{
|
||||
mem_reserve_function reserve;
|
||||
mem_modify_function commit;
|
||||
mem_modify_function decommit;
|
||||
mem_modify_function release;
|
||||
void* context;
|
||||
|
||||
} mem_base_allocator;
|
||||
|
||||
mem_base_allocator* mem_base_allocator_default();
|
||||
|
||||
#define mem_base_reserve(base, size) base->reserve(base->context, size)
|
||||
#define mem_base_commit(base, ptr, size) base->commit(base->context, ptr, size)
|
||||
#define mem_base_decommit(base, ptr, size) base->decommit(base->context, ptr, size)
|
||||
#define mem_base_release(base, ptr, size) base->release(base->context, ptr, size)
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//NOTE(martin): memory arena
|
||||
//--------------------------------------------------------------------------------
|
||||
typedef struct mem_arena
|
||||
{
|
||||
mem_base_allocator* base;
|
||||
char* ptr;
|
||||
u64 offset;
|
||||
u64 committed;
|
||||
u64 cap;
|
||||
} mem_arena;
|
||||
|
||||
typedef struct mem_arena_options
|
||||
{
|
||||
mem_base_allocator* base;
|
||||
u64 reserve;
|
||||
} mem_arena_options;
|
||||
|
||||
void mem_arena_init(mem_arena* arena);
|
||||
void mem_arena_init_with_options(mem_arena* arena, mem_arena_options* options);
|
||||
void mem_arena_release(mem_arena* arena);
|
||||
|
||||
void* mem_arena_alloc(mem_arena* arena, u64 size);
|
||||
void mem_arena_clear(mem_arena* arena);
|
||||
|
||||
#define mem_arena_alloc_type(arena, type) ((type*)mem_arena_alloc(arena, sizeof(type)))
|
||||
#define mem_arena_alloc_array(arena, type, count) ((type*)mem_arena_alloc(arena, sizeof(type)*(count)))
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//NOTE(martin): memory pool
|
||||
//--------------------------------------------------------------------------------
|
||||
typedef struct mem_pool
|
||||
{
|
||||
mem_arena arena;
|
||||
list_info freeList;
|
||||
u64 blockSize;
|
||||
} mem_pool;
|
||||
|
||||
typedef struct mem_pool_options
|
||||
{
|
||||
mem_base_allocator* base;
|
||||
u64 reserve;
|
||||
} mem_pool_options;
|
||||
|
||||
void mem_pool_init(mem_pool* pool, u64 blockSize);
|
||||
void mem_pool_init_with_options(mem_pool* pool, u64 blockSize, mem_pool_options* options);
|
||||
void mem_pool_release(mem_pool* pool);
|
||||
|
||||
void* mem_pool_alloc(mem_pool* pool);
|
||||
void mem_pool_recycle(mem_pool* pool, void* ptr);
|
||||
void mem_pool_clear(mem_pool* pool);
|
||||
|
||||
#define mem_pool_alloc_type(arena, type) ((type*)mem_pool_alloc(arena))
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//NOTE(martin): per-thread implicit scratch arena
|
||||
//--------------------------------------------------------------------------------
|
||||
void mem_scratch_clear();
|
||||
mem_arena* mem_scratch();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif //__MEMORY_H_
|
|
@ -0,0 +1,94 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: ringbuffer.cpp
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 31/07/2022
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#include<stdlib.h> // malloc, free
|
||||
#include"ringbuffer.h"
|
||||
|
||||
void ringbuffer_init(ringbuffer* ring, u8 capExp)
|
||||
{
|
||||
u32 cap = 1<<capExp;
|
||||
ring->mask = cap - 1;
|
||||
ring->readIndex = 0;
|
||||
ring->writeIndex = 0;
|
||||
ring->buffer = (u8*)malloc(cap);
|
||||
}
|
||||
|
||||
void ringbuffer_cleanup(ringbuffer* ring)
|
||||
{
|
||||
free(ring->buffer);
|
||||
}
|
||||
|
||||
u32 ringbuffer_read_available(ringbuffer* ring)
|
||||
{
|
||||
return((ring->writeIndex - ring->readIndex) & ring->mask);
|
||||
}
|
||||
|
||||
u32 ringbuffer_write_available(ringbuffer* ring)
|
||||
{
|
||||
//NOTE(martin): we keep one sentinel byte between write index and read index,
|
||||
// when the buffer is full, to avoid overrunning read index.
|
||||
// Hence, available write space is size - 1 - available read space.
|
||||
return(ring->mask - ringbuffer_read_available(ring));
|
||||
}
|
||||
|
||||
u32 ringbuffer_write(ringbuffer* ring, u32 size, u8* data)
|
||||
{
|
||||
u32 read = ring->readIndex;
|
||||
u32 write = ring->writeIndex;
|
||||
|
||||
u32 writeAvailable = ringbuffer_write_available(ring);
|
||||
if(size > writeAvailable)
|
||||
{
|
||||
DEBUG_ASSERT("not enough space available");
|
||||
size = writeAvailable;
|
||||
}
|
||||
|
||||
if(read <= write)
|
||||
{
|
||||
u32 copyCount = minimum(size, ring->mask + 1 - write);
|
||||
memcpy(ring->buffer + write, data, copyCount);
|
||||
|
||||
data += copyCount;
|
||||
copyCount = size - copyCount;
|
||||
memcpy(ring->buffer, data, copyCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(ring->buffer + write, data, size);
|
||||
}
|
||||
ring->writeIndex = (write + size) & ring->mask;
|
||||
return(size);
|
||||
}
|
||||
|
||||
u32 ringbuffer_read(ringbuffer* ring, u32 size, u8* data)
|
||||
{
|
||||
u32 read = ring->readIndex;
|
||||
u32 write = ring->writeIndex;
|
||||
|
||||
u32 readAvailable = ringbuffer_read_available(ring);
|
||||
if(size > readAvailable)
|
||||
{
|
||||
size = readAvailable;
|
||||
}
|
||||
|
||||
if(read <= write)
|
||||
{
|
||||
memcpy(data, ring->buffer + read, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 copyCount = minimum(size, ring->mask + 1 - read);
|
||||
memcpy(data, ring->buffer + read, copyCount);
|
||||
|
||||
data += copyCount;
|
||||
copyCount = size - copyCount;
|
||||
memcpy(data, ring->buffer, copyCount);
|
||||
}
|
||||
ring->readIndex = (read + size) & ring->mask;
|
||||
return(size);
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: ringbuffer.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 31/07/2022
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __RINGBUFFER_H_
|
||||
#define __RINGBUFFER_H_
|
||||
|
||||
#include<stdatomic.h>
|
||||
#include"typedefs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct ringbuffer
|
||||
{
|
||||
u32 mask;
|
||||
_Atomic(u32) readIndex;
|
||||
_Atomic(u32) writeIndex;
|
||||
|
||||
u8* buffer;
|
||||
|
||||
} ringbuffer;
|
||||
|
||||
void ringbuffer_init(ringbuffer* ring, u8 capExp);
|
||||
void ringbuffer_cleanup(ringbuffer* ring);
|
||||
u32 ringbuffer_read_available(ringbuffer* ring);
|
||||
u32 ringbuffer_write_available(ringbuffer* ring);
|
||||
u32 ringbuffer_write(ringbuffer* ring, u32 size, u8* data);
|
||||
u32 ringbuffer_read(ringbuffer* ring, u32 size, u8* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif //__RINGBUFFER_H_
|
|
@ -0,0 +1,298 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: strings.c
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 29/05/2021
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#include<string.h> // strlen(), strcpy(), etc.
|
||||
#include<stdarg.h> // va_list() etc.
|
||||
#include"debug_log.h"
|
||||
#include"strings.h"
|
||||
|
||||
#define LOG_SUBSYSTEM "Strings"
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// string slices as values
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
str8 str8_from_buffer(u64 len, char* buffer)
|
||||
{
|
||||
return((str8){.len = len, .ptr = buffer});
|
||||
}
|
||||
|
||||
str8 str8_from_cstring(char* str)
|
||||
{
|
||||
return(str8_from_buffer(strlen(str), (char*)str));
|
||||
}
|
||||
|
||||
str8 str8_slice(str8 s, u64 start, u64 end)
|
||||
{
|
||||
ASSERT(start <= end && start <= s.len && end <= s.len);
|
||||
return((str8){.len = end - start, .ptr = s.ptr + start});
|
||||
}
|
||||
|
||||
str8 str8_push_buffer(mem_arena* arena, u64 len, char* buffer)
|
||||
{
|
||||
str8 str = {};
|
||||
str.len = len;
|
||||
str.ptr = mem_arena_alloc_array(arena, char, len);
|
||||
memcpy(str.ptr, buffer, len);
|
||||
return(str);
|
||||
}
|
||||
|
||||
str8 str8_push_cstring(mem_arena* arena, const char* str)
|
||||
{
|
||||
return(str8_push_buffer(arena, strlen(str), (char*)str));
|
||||
}
|
||||
|
||||
str8 str8_push_copy(mem_arena* arena, str8 s)
|
||||
{
|
||||
return(str8_push_buffer(arena, str8_unbox(s)));
|
||||
}
|
||||
|
||||
str8 str8_push_slice(mem_arena* arena, str8 s, u64 start, u64 end)
|
||||
{
|
||||
str8 slice = str8_slice(s, start, end);
|
||||
return(str8_push_copy(arena, slice));
|
||||
}
|
||||
|
||||
str8 str8_pushfv(mem_arena* arena, const char* format, va_list args)
|
||||
{
|
||||
//NOTE(martin):
|
||||
// We first compute the number of characters to write passing a size of 0.
|
||||
// then we allocate len+1 (since vsnprint always terminates with a '\0').
|
||||
// We could call vsprintf since we know the size, but I'm not sure there's a hard
|
||||
// guarantee that vsprintf and vsnprintf write the same number of characters in all
|
||||
// and every case, and that would be a difficult bug to spot, so it seems better to
|
||||
// waste one byte and be safe.
|
||||
char dummy;
|
||||
str8 str = {};
|
||||
va_list argCopy;
|
||||
va_copy(argCopy, args);
|
||||
str.len = vsnprintf(&dummy, 0, format, argCopy);
|
||||
va_end(argCopy);
|
||||
|
||||
str.ptr = mem_arena_alloc_array(arena, char, str.len + 1);
|
||||
vsnprintf((char*)str.ptr, str.len + 1, format, args);
|
||||
return(str);
|
||||
}
|
||||
|
||||
str8 str8_pushf(mem_arena* arena, const char* format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
str8 str = str8_pushfv(arena, format, args);
|
||||
va_end(args);
|
||||
return(str);
|
||||
}
|
||||
|
||||
int str8_cmp(str8 s1, str8 s2)
|
||||
{
|
||||
int res = strncmp(s1.ptr, s2.ptr, minimum(s1.len, s2.len));
|
||||
if(!res)
|
||||
{
|
||||
res = (s1.len < s2.len)? -1 : ((s1.len == s2.len)? 0 : 1);
|
||||
}
|
||||
return(res);
|
||||
}
|
||||
|
||||
char* str8_to_cstring(mem_arena* arena, str8 string)
|
||||
{
|
||||
char* cstr = mem_arena_alloc_array(arena, char, string.len+1);
|
||||
memcpy(cstr, string.ptr, string.len);
|
||||
cstr[string.len] = '\0';
|
||||
return(cstr);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// string lists
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
void str8_list_init(str8_list* list)
|
||||
{
|
||||
ListInit(&list->list);
|
||||
list->eltCount = 0;
|
||||
list->len = 0;
|
||||
}
|
||||
|
||||
void str8_list_push(mem_arena* arena, str8_list* list, str8 str)
|
||||
{
|
||||
str8_elt* elt = mem_arena_alloc_type(arena, str8_elt);
|
||||
elt->string = str;
|
||||
ListAppend(&list->list, &elt->listElt);
|
||||
list->eltCount++;
|
||||
list->len += str.len;
|
||||
}
|
||||
|
||||
void str8_list_pushf(mem_arena* arena, str8_list* list, const char* format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
str8 str = str8_pushfv(arena, format, args);
|
||||
va_end(args);
|
||||
str8_list_push(arena, list, str);
|
||||
}
|
||||
|
||||
str8 str8_list_collate(mem_arena* arena, str8_list list, str8 prefix, str8 separator, str8 postfix)
|
||||
{
|
||||
str8 str = {};
|
||||
str.len = prefix.len + list.len + list.eltCount*separator.len + postfix.len;
|
||||
str.ptr = mem_arena_alloc_array(arena, char, str.len);
|
||||
char* dst = str.ptr;
|
||||
memcpy(dst, prefix.ptr, prefix.len);
|
||||
dst += prefix.len;
|
||||
|
||||
str8_elt* elt = ListFirstEntry(&list.list, str8_elt, listElt);
|
||||
if(elt)
|
||||
{
|
||||
memcpy(dst, elt->string.ptr, elt->string.len);
|
||||
dst += elt->string.len;
|
||||
elt = ListNextEntry(&list.list, elt, str8_elt, listElt);
|
||||
}
|
||||
|
||||
for( ; elt != 0; elt = ListNextEntry(&list.list, elt, str8_elt, listElt))
|
||||
{
|
||||
memcpy(dst, separator.ptr, separator.len);
|
||||
dst += separator.len;
|
||||
memcpy(dst, elt->string.ptr, elt->string.len);
|
||||
dst += elt->string.len;
|
||||
}
|
||||
memcpy(dst, postfix.ptr, postfix.len);
|
||||
return(str);
|
||||
}
|
||||
|
||||
str8 str8_list_join(mem_arena* arena, str8_list list)
|
||||
{
|
||||
str8 empty = {.len = 0, .ptr = 0};
|
||||
return(str8_list_collate(arena, list, empty, empty, empty));
|
||||
}
|
||||
|
||||
str8_list str8_split(mem_arena* arena, str8 str, str8_list separators)
|
||||
{
|
||||
str8_list list = {};
|
||||
ListInit(&list.list);
|
||||
|
||||
char* ptr = str.ptr;
|
||||
char* end = str.ptr + str.len;
|
||||
char* subStart = ptr;
|
||||
for(; ptr < end; ptr++)
|
||||
{
|
||||
//NOTE(martin): search all separators and try to match them to the current ptr
|
||||
str8* foundSep = 0;
|
||||
for_each_in_list(&separators.list, elt, str8_elt, listElt)
|
||||
{
|
||||
str8* separator = &elt->string;
|
||||
bool equal = true;
|
||||
for(u64 offset = 0;
|
||||
(offset < separator->len) && (ptr+offset < end);
|
||||
offset++)
|
||||
{
|
||||
if(separator->ptr[offset] != ptr[offset])
|
||||
{
|
||||
equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(equal)
|
||||
{
|
||||
foundSep = separator;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(foundSep)
|
||||
{
|
||||
//NOTE(martin): we found a separator. If the start of the current substring is != ptr,
|
||||
// the current substring is not empty and we emit the substring
|
||||
if(ptr != subStart)
|
||||
{
|
||||
str8 sub = str8_from_buffer(ptr-subStart, subStart);
|
||||
str8_list_push(arena, &list, sub);
|
||||
}
|
||||
ptr += foundSep->len - 1; //NOTE(martin): ptr is incremented at the end of the loop
|
||||
subStart = ptr+1;
|
||||
}
|
||||
}
|
||||
//NOTE(martin): emit the last substring
|
||||
if(ptr != subStart)
|
||||
{
|
||||
str8 sub = str8_from_buffer(ptr-subStart, subStart);
|
||||
str8_list_push(arena, &list, sub);
|
||||
}
|
||||
return(list);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// u32 strings
|
||||
//----------------------------------------------------------------------------------
|
||||
str32 str32_from_buffer(u64 len, u32* buffer)
|
||||
{
|
||||
return((str32){.len = len, .ptr = buffer});
|
||||
}
|
||||
|
||||
str32 str32_slice(str32 s, u64 start, u64 end)
|
||||
{
|
||||
ASSERT(start <= end && start <= s.len && end <= s.len);
|
||||
return((str32){.len = end - start, .ptr = s.ptr + start});
|
||||
}
|
||||
|
||||
str32 str32_push_buffer(mem_arena* arena, u64 len, u32* buffer)
|
||||
{
|
||||
str32 str = {};
|
||||
str.len = len;
|
||||
str.ptr = mem_arena_alloc_array(arena, u32, len);
|
||||
memcpy(str.ptr, buffer, len*sizeof(u32));
|
||||
return(str);
|
||||
}
|
||||
|
||||
str32 str32_push_copy(mem_arena* arena, str32 s)
|
||||
{
|
||||
return(str32_push_buffer(arena, s.len, s.ptr));
|
||||
}
|
||||
|
||||
str32 str32_push_slice(mem_arena* arena, str32 s, u64 start, u64 end)
|
||||
{
|
||||
str32 slice = str32_slice(s, start, end);
|
||||
return(str32_push_copy(arena, slice));
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Paths helpers
|
||||
//----------------------------------------------------------------------------------
|
||||
str8 mp_path_directory(str8 fullPath)
|
||||
{
|
||||
i64 lastSlashIndex = -1;
|
||||
|
||||
for(i64 i = fullPath.len-1; i >= 0; i--)
|
||||
{
|
||||
if(fullPath.ptr[i] == '/')
|
||||
{
|
||||
lastSlashIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
str8 directory = str8_slice(fullPath, 0, lastSlashIndex+1);
|
||||
return(directory);
|
||||
}
|
||||
|
||||
str8 mp_path_base_name(str8 fullPath)
|
||||
{
|
||||
i64 lastSlashIndex = -1;
|
||||
|
||||
for(i64 i = fullPath.len-1; i >= 0; i--)
|
||||
{
|
||||
if(fullPath.ptr[i] == '/')
|
||||
{
|
||||
lastSlashIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
str8 basename = str8_slice(fullPath, lastSlashIndex+1, fullPath.len);
|
||||
return(basename);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#undef LOG_SUBSYSTEM
|
|
@ -0,0 +1,96 @@
|
|||
/************************************************************//**
|
||||
*
|
||||
* @file: strings.h
|
||||
* @author: Martin Fouilleul
|
||||
* @date: 29/05/2021
|
||||
* @revision:
|
||||
*
|
||||
*****************************************************************/
|
||||
#ifndef __STRINGS_H_
|
||||
#define __STRINGS_H_
|
||||
|
||||
#include"typedefs.h"
|
||||
#include"lists.h"
|
||||
#include"memory.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// string slices as values
|
||||
//----------------------------------------------------------------------------------
|
||||
typedef struct str8
|
||||
{
|
||||
u64 len;
|
||||
char* ptr;
|
||||
} str8;
|
||||
|
||||
#define str8_lit(s) ((str8){.len = sizeof(s)-1, .ptr = (char*)(s)})
|
||||
#define str8_unbox(s) (int)((s).len), ((s).ptr)
|
||||
|
||||
str8 str8_from_buffer(u64 len, char* buffer);
|
||||
str8 str8_from_cstring(char* str);
|
||||
str8 str8_slice(str8 s, u64 start, u64 end);
|
||||
|
||||
str8 str8_push_buffer(mem_arena* arena, u64 len, char* buffer);
|
||||
str8 str8_push_cstring(mem_arena* arena, const char* str);
|
||||
str8 str8_push_copy(mem_arena* arena, str8 s);
|
||||
str8 str8_push_slice(mem_arena* arena, str8 s, u64 start, u64 end);
|
||||
|
||||
str8 str8_pushfv(mem_arena* arena, const char* format, va_list args);
|
||||
str8 str8_pushf(mem_arena* arena, const char* format, ...);
|
||||
|
||||
int str8_cmp(str8 s1, str8 s2);
|
||||
|
||||
char* str8_to_cstring(mem_arena* arena, str8 string);
|
||||
//----------------------------------------------------------------------------------
|
||||
// string lists
|
||||
//----------------------------------------------------------------------------------
|
||||
typedef struct str8_elt
|
||||
{
|
||||
list_elt listElt;
|
||||
str8 string;
|
||||
} str8_elt;
|
||||
|
||||
typedef struct str8_list
|
||||
{
|
||||
list_info list;
|
||||
u64 eltCount;
|
||||
u64 len;
|
||||
} str8_list;
|
||||
|
||||
void str8_list_push(mem_arena* arena, str8_list* list, str8 str);
|
||||
void str8_list_pushf(mem_arena* arena, str8_list* list, const char* format, ...);
|
||||
|
||||
str8 str8_list_join(mem_arena* arena, str8_list list);
|
||||
str8_list str8_split(mem_arena* arena, str8 str, str8_list separators);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// u32 strings
|
||||
//----------------------------------------------------------------------------------
|
||||
typedef struct str32
|
||||
{
|
||||
u64 len;
|
||||
u32* ptr;
|
||||
} str32;
|
||||
|
||||
str32 str32_from_buffer(u64 len, u32* buffer);
|
||||
str32 str32_slice(str32 s, u64 start, u64 end);
|
||||
|
||||
str32 str32_push_buffer(mem_arena* arena, u64 len, u32* buffer);
|
||||
str32 str32_push_copy(mem_arena* arena, str32 s);
|
||||
str32 str32_push_slice(mem_arena* arena, str32 s, u64 start, u64 end);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Paths helpers
|
||||
//----------------------------------------------------------------------------------
|
||||
str8 mp_path_directory(str8 fullPath);
|
||||
str8 mp_path_base_name(str8 fullPath);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
|
||||
#endif //__STRINGS_H_
|
|
@ -0,0 +1,70 @@
|
|||
//*****************************************************************
|
||||
//
|
||||
// $file: typedefs.h $
|
||||
// $author: Martin Fouilleul $
|
||||
// $date: 23/36/2015 $
|
||||
// $revision: $
|
||||
// $note: (C) 2015 by Martin Fouilleul - all rights reserved $
|
||||
//
|
||||
//*****************************************************************
|
||||
#ifndef __TYPEDEFS_H_
|
||||
#define __TYPEDEFS_H_
|
||||
|
||||
#include<inttypes.h>
|
||||
#include<float.h> //FLT_MAX/MIN etc...
|
||||
|
||||
#ifndef __cplusplus
|
||||
#include<stdbool.h>
|
||||
#endif //__cplusplus
|
||||
|
||||
typedef uint8_t byte;
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
|
||||
typedef float f32;
|
||||
typedef double f64;
|
||||
|
||||
typedef union
|
||||
{
|
||||
struct
|
||||
{
|
||||
f32 x;
|
||||
f32 y;
|
||||
};
|
||||
f32 c[2];
|
||||
} vec2;
|
||||
|
||||
typedef union
|
||||
{
|
||||
struct
|
||||
{
|
||||
f32 x;
|
||||
f32 y;
|
||||
f32 z;
|
||||
f32 w;
|
||||
};
|
||||
f32 c[4];
|
||||
} vec4;
|
||||
|
||||
#define vec4_expand_xyz(v) (v).x, (v).y, (v).z
|
||||
|
||||
typedef union
|
||||
{
|
||||
struct
|
||||
{
|
||||
f32 x;
|
||||
f32 y;
|
||||
f32 w;
|
||||
f32 h;
|
||||
};
|
||||
f32 c[4];
|
||||
} mp_rect;
|
||||
|
||||
#endif //__TYPEDEFS_H_
|
|
@ -0,0 +1,283 @@
|
|||
//*****************************************************************
|
||||
//
|
||||
// $file: utf8.c $
|
||||
// $author: Martin Fouilleul $
|
||||
// $date: 05/11/2016 $
|
||||
// $revision: $
|
||||
// $note: (C) 2016 by Martin Fouilleul - all rights reserved $
|
||||
//
|
||||
//*****************************************************************
|
||||
#include"utf8.h"
|
||||
#include<string.h>
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// utf-8 gore
|
||||
//-----------------------------------------------------------------
|
||||
const u_int32_t offsetsFromUTF8[6] = {
|
||||
0x00000000UL, 0x00003080UL, 0x000E2080UL,
|
||||
0x03C82080UL, 0xFA082080UL, 0x82082080UL };
|
||||
|
||||
const char trailingBytesForUTF8[256] = {
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
|
||||
};
|
||||
|
||||
#define utf8_is_start_byte(c) (((c)&0xc0)!=0x80)
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
//NOTE: getting sizes / offsets / indices
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
u32 utf8_size_from_leading_char(char leadingChar)
|
||||
{
|
||||
return(trailingBytesForUTF8[(unsigned int)(unsigned char)leadingChar] + 1);
|
||||
}
|
||||
|
||||
u32 utf8_codepoint_size(utf32 codePoint)
|
||||
{
|
||||
if(codePoint < 0x80)
|
||||
{
|
||||
return(1);
|
||||
}
|
||||
if(codePoint < 0x800)
|
||||
{
|
||||
return(2);
|
||||
}
|
||||
if(codePoint < 0x10000)
|
||||
{
|
||||
return(3);
|
||||
}
|
||||
if(codePoint < 0x110000)
|
||||
{
|
||||
return(4);
|
||||
}
|
||||
return(0);
|
||||
}
|
||||
|
||||
u64 utf8_codepoint_count_for_string(str8 string)
|
||||
{
|
||||
u64 byteOffset = 0;
|
||||
u64 codePointIndex = 0;
|
||||
for(;
|
||||
(byteOffset < string.len) && (string.ptr[byteOffset] != 0);
|
||||
codePointIndex++)
|
||||
{
|
||||
utf8_dec decode = utf8_decode_at(string, byteOffset);
|
||||
byteOffset += decode.size;
|
||||
}
|
||||
return(codePointIndex);
|
||||
}
|
||||
|
||||
u64 utf8_byte_count_for_codepoints(str32 codePoints)
|
||||
{
|
||||
//NOTE(martin): return the exact number of bytes taken by the encoded
|
||||
// version of codePoints. (ie do not attempt to provision
|
||||
// for a zero terminator).
|
||||
u64 byteCount = 0;
|
||||
for(u64 i=0; i<codePoints.len; i++)
|
||||
{
|
||||
byteCount += utf8_codepoint_size(codePoints.ptr[i]);
|
||||
}
|
||||
return(byteCount);
|
||||
}
|
||||
|
||||
u64 utf8_next_offset(str8 string, u64 byteOffset)
|
||||
{
|
||||
u64 res = 0;
|
||||
if(byteOffset >= string.len)
|
||||
{
|
||||
res = string.len;
|
||||
}
|
||||
else
|
||||
{
|
||||
u64 nextOffset = byteOffset + utf8_size_from_leading_char(string.ptr[byteOffset]);
|
||||
res = minimum(nextOffset, string.len);
|
||||
}
|
||||
return(res);
|
||||
}
|
||||
|
||||
u64 utf8_prev_offset(str8 string, u64 byteOffset)
|
||||
{
|
||||
u64 res = 0;
|
||||
if(byteOffset > string.len)
|
||||
{
|
||||
res = string.len;
|
||||
}
|
||||
else if(byteOffset)
|
||||
{
|
||||
byteOffset--;
|
||||
while(byteOffset > 0 && !utf8_is_start_byte(string.ptr[byteOffset]))
|
||||
{
|
||||
byteOffset--;
|
||||
}
|
||||
res = byteOffset;
|
||||
}
|
||||
return(res);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
//NOTE: encoding / decoding
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
utf8_dec utf8_decode_at(str8 string, u64 offset)
|
||||
{
|
||||
//NOTE(martin): get the first codepoint in str, and advance index to the
|
||||
// next utf8 character
|
||||
//TODO(martin): check for utf-16 surrogate pairs
|
||||
utf32 cp = 0;
|
||||
u64 sz = 0;
|
||||
|
||||
if(offset >= string.len || !string.ptr[offset])
|
||||
{
|
||||
cp = 0;
|
||||
sz = 1;
|
||||
}
|
||||
else if( !utf8_is_start_byte(string.ptr[offset]))
|
||||
{
|
||||
//NOTE(martin): unexpected continuation or invalid character.
|
||||
cp = 0xfffd;
|
||||
sz = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
int expectedSize = utf8_size_from_leading_char(string.ptr[offset]);
|
||||
do
|
||||
{
|
||||
/*NOTE(martin):
|
||||
we shift 6 bits and add the next byte at each round.
|
||||
at the end we have our utf8 codepoint, added to the shifted versions
|
||||
of the utf8 leading bits for each encoded byte. These values are
|
||||
precomputed in offsetsFromUTF8.
|
||||
*/
|
||||
unsigned char b = string.ptr[offset];
|
||||
cp <<= 6;
|
||||
cp += b;
|
||||
offset += 1;
|
||||
sz++;
|
||||
|
||||
if(b == 0xc0 || b == 0xc1 || b >= 0xc5)
|
||||
{
|
||||
//NOTE(martin): invalid byte encountered
|
||||
break;
|
||||
}
|
||||
|
||||
} while( offset < string.len
|
||||
&& string.ptr[offset]
|
||||
&& !utf8_is_start_byte(string.ptr[offset])
|
||||
&& sz < expectedSize);
|
||||
|
||||
if(sz != expectedSize)
|
||||
{
|
||||
//NOTE(martin): if we encountered an error, we return the replacement codepoint U+FFFD
|
||||
cp = 0xfffd;
|
||||
}
|
||||
else
|
||||
{
|
||||
cp -= offsetsFromUTF8[sz-1];
|
||||
|
||||
//NOTE(martin): check for invalid codepoints
|
||||
if(cp > 0x10ffff || (cp >= 0xd800 && cp <= 0xdfff))
|
||||
{
|
||||
cp = 0xfffd;
|
||||
}
|
||||
}
|
||||
}
|
||||
utf8_dec res = {.codepoint = cp, .size = sz};
|
||||
return(res);
|
||||
}
|
||||
|
||||
utf8_dec utf8_decode(str8 string)
|
||||
{
|
||||
return(utf8_decode_at(string, 0));
|
||||
}
|
||||
|
||||
str8 utf8_encode(char* dest, utf32 codePoint)
|
||||
{
|
||||
u64 sz = 0;
|
||||
if (codePoint < 0x80)
|
||||
{
|
||||
dest[0] = (char)codePoint;
|
||||
sz = 1;
|
||||
}
|
||||
else if (codePoint < 0x800)
|
||||
{
|
||||
dest[0] = (codePoint>>6) | 0xC0;
|
||||
dest[1] = (codePoint & 0x3F) | 0x80;
|
||||
sz = 2;
|
||||
}
|
||||
else if (codePoint < 0x10000)
|
||||
{
|
||||
dest[0] = (codePoint>>12) | 0xE0;
|
||||
dest[1] = ((codePoint>>6) & 0x3F) | 0x80;
|
||||
dest[2] = (codePoint & 0x3F) | 0x80;
|
||||
sz = 3;
|
||||
}
|
||||
else if (codePoint < 0x110000)
|
||||
{
|
||||
dest[0] = (codePoint>>18) | 0xF0;
|
||||
dest[1] = ((codePoint>>12) & 0x3F) | 0x80;
|
||||
dest[2] = ((codePoint>>6) & 0x3F) | 0x80;
|
||||
dest[3] = (codePoint & 0x3F) | 0x80;
|
||||
sz = 4;
|
||||
}
|
||||
str8 res = {.len = sz, .ptr = dest};
|
||||
return(res);
|
||||
}
|
||||
|
||||
str32 utf8_to_codepoints(u64 maxCount, utf32* backing, str8 string)
|
||||
{
|
||||
u64 codePointIndex = 0;
|
||||
u64 byteOffset = 0;
|
||||
for(; codePointIndex < maxCount && byteOffset < string.len; codePointIndex++)
|
||||
{
|
||||
utf8_dec decode = utf8_decode_at(string, byteOffset);
|
||||
backing[codePointIndex] = decode.codepoint;
|
||||
byteOffset += decode.size;
|
||||
}
|
||||
str32 res = {.len = codePointIndex, .ptr = backing};
|
||||
return(res);
|
||||
}
|
||||
|
||||
str8 utf8_from_codepoints(u64 maxBytes, char* backing, str32 codePoints)
|
||||
{
|
||||
u64 byteOffset = 0;
|
||||
for(u64 codePointIndex = 0; (codePointIndex < codePoints.len); codePointIndex++)
|
||||
{
|
||||
utf32 codePoint = codePoints.ptr[codePointIndex];
|
||||
u32 byteCount = utf8_codepoint_size(codePoint);
|
||||
if(byteOffset + byteCount > maxBytes)
|
||||
{
|
||||
break;
|
||||
}
|
||||
utf8_encode(backing+byteOffset, codePoint);
|
||||
byteOffset += byteCount;
|
||||
}
|
||||
str8 res = {.len = byteOffset, .ptr = backing};
|
||||
return(res);
|
||||
}
|
||||
|
||||
str32 utf8_push_to_codepoints(mem_arena* arena, str8 string)
|
||||
{
|
||||
u64 count = utf8_codepoint_count_for_string(string);
|
||||
utf32* backing = mem_arena_alloc_array(arena, utf32, count);
|
||||
str32 res = utf8_to_codepoints(count, backing, string);
|
||||
return(res);
|
||||
}
|
||||
|
||||
str8 utf8_push_from_codepoints(mem_arena* arena, str32 codePoints)
|
||||
{
|
||||
u64 count = utf8_byte_count_for_codepoints(codePoints);
|
||||
char* backing = mem_arena_alloc_array(arena, char, count);
|
||||
str8 res = utf8_from_codepoints(count, backing, codePoints);
|
||||
return(res);
|
||||
}
|
||||
|
||||
#define UNICODE_RANGE(start, cnt, name) const unicode_range _cat2_(UNICODE_RANGE_, name) = { .firstCodePoint = start, .count = cnt };
|
||||
UNICODE_RANGES
|
||||
#undef UNICODE_RANGE
|
|
@ -0,0 +1,201 @@
|
|||
//*****************************************************************
|
||||
//
|
||||
// $file: utf8.h $
|
||||
// $author: Martin Fouilleul $
|
||||
// $date: 05/11/2016 $
|
||||
// $revision: $
|
||||
// $note: (C) 2016 by Martin Fouilleul - all rights reserved $
|
||||
//
|
||||
//*****************************************************************
|
||||
#ifndef __UTF8_H_
|
||||
#define __UTF8_H_
|
||||
|
||||
#include"typedefs.h"
|
||||
#include"macro_helpers.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef u32 utf32;
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
//NOTE: getting sizes / offsets / indices
|
||||
//-----------------------------------------------------------------
|
||||
u32 utf8_size_from_leading_char(char leadingChar);
|
||||
u32 utf8_codepoint_size(utf32 codePoint);
|
||||
|
||||
u64 utf8_codepoint_count_for_string(str8 string);
|
||||
u64 utf8_byte_count_for_codepoints(str32 codePoints);
|
||||
|
||||
u64 utf8_next_offset(str8 string, u64 byteOffset);
|
||||
u64 utf8_prev_offset(str8 string, u64 byteOffset);
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
//NOTE: encoding / decoding
|
||||
//-----------------------------------------------------------------
|
||||
typedef struct utf8_dec
|
||||
{
|
||||
utf32 codepoint; //NOTE: decoded codepoint
|
||||
u32 size; //NOTE: size of corresponding utf8 sequence
|
||||
} utf8_dec;
|
||||
|
||||
utf8_dec utf8_decode(str8 string); //NOTE: decode a single utf8 sequence at start of string
|
||||
utf8_dec utf8_decode_at(str8 string, u64 offset); //NOTE: decode a single utf8 sequence starting at byte offset
|
||||
str8 utf8_encode(char* dst, utf32 codePoint); //NOTE: encode codepoint into backing buffer dst
|
||||
|
||||
str32 utf8_to_codepoints(u64 maxCount, utf32* backing, str8 string);
|
||||
str8 utf8_from_codepoints(u64 maxBytes, char* backing, str32 codePoints);
|
||||
|
||||
str32 utf8_push_to_codepoints(mem_arena* arena, str8 string);
|
||||
str8 utf8_push_from_codepoints(mem_arena* arena, str32 codePoints);
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// utf8 range struct and X-macros for defining utf8 ranges
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
typedef struct unicode_range
|
||||
{
|
||||
utf32 firstCodePoint;
|
||||
u32 count;
|
||||
} unicode_range;
|
||||
|
||||
//NOTE(martin): range declared here are defined in utf8.cpp
|
||||
// they can be used by prefixing them with UTF8_RANGE_, as in 'UTF8_RANGE_BASIC_LATIN'
|
||||
#define UNICODE_RANGES \
|
||||
UNICODE_RANGE(0x0000, 127, BASIC_LATIN) \
|
||||
UNICODE_RANGE(0x0080, 127, C1_CONTROLS_AND_LATIN_1_SUPPLEMENT) \
|
||||
UNICODE_RANGE(0x0100, 127, LATIN_EXTENDED_A) \
|
||||
UNICODE_RANGE(0x0180, 207, LATIN_EXTENDED_B) \
|
||||
UNICODE_RANGE(0x0250, 95, IPA_EXTENSIONS) \
|
||||
UNICODE_RANGE(0x02b0, 79, SPACING_MODIFIER_LETTERS) \
|
||||
UNICODE_RANGE(0x0300, 111, COMBINING_DIACRITICAL_MARKS) \
|
||||
UNICODE_RANGE(0x0370, 143, GREEK_COPTIC) \
|
||||
UNICODE_RANGE(0x0400, 255, CYRILLIC) \
|
||||
UNICODE_RANGE(0x0500, 47, CYRILLIC_SUPPLEMENT) \
|
||||
UNICODE_RANGE(0x0530, 95, ARMENIAN) \
|
||||
UNICODE_RANGE(0x0590, 111, HEBREW) \
|
||||
UNICODE_RANGE(0x0600, 255, ARABIC) \
|
||||
UNICODE_RANGE(0x0700, 79, SYRIAC) \
|
||||
UNICODE_RANGE(0x0780, 63, THAANA) \
|
||||
UNICODE_RANGE(0x0900, 127, DEVANAGARI) \
|
||||
UNICODE_RANGE(0x0980, 127, BENGALI_ASSAMESE) \
|
||||
UNICODE_RANGE(0x0a00, 127, GURMUKHI) \
|
||||
UNICODE_RANGE(0x0a80, 127, GUJARATI) \
|
||||
UNICODE_RANGE(0x0b00, 127, ORIYA) \
|
||||
UNICODE_RANGE(0x0b80, 127, TAMIL) \
|
||||
UNICODE_RANGE(0x0c00, 127, TELUGU) \
|
||||
UNICODE_RANGE(0x0c80, 127, KANNADA) \
|
||||
UNICODE_RANGE(0x0d00, 255, MALAYALAM) \
|
||||
UNICODE_RANGE(0x0d80, 127, SINHALA) \
|
||||
UNICODE_RANGE(0x0e00, 127, THAI) \
|
||||
UNICODE_RANGE(0x0e80, 127, LAO) \
|
||||
UNICODE_RANGE(0x0f00, 255, TIBETAN) \
|
||||
UNICODE_RANGE(0x1000, 159, MYANMAR) \
|
||||
UNICODE_RANGE(0x10a0, 95, GEORGIAN) \
|
||||
UNICODE_RANGE(0x1100, 255, HANGUL_JAMO) \
|
||||
UNICODE_RANGE(0x1200, 383, ETHIOPIC) \
|
||||
UNICODE_RANGE(0x13a0, 95, CHEROKEE) \
|
||||
UNICODE_RANGE(0x1400, 639, UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS) \
|
||||
UNICODE_RANGE(0x1680, 31, OGHAM) \
|
||||
UNICODE_RANGE(0x16a0, 95, RUNIC) \
|
||||
UNICODE_RANGE(0x1700, 31, TAGALOG) \
|
||||
UNICODE_RANGE(0x1720, 31, HANUNOO) \
|
||||
UNICODE_RANGE(0x1740, 31, BUHID) \
|
||||
UNICODE_RANGE(0x1760, 31, TAGBANWA) \
|
||||
UNICODE_RANGE(0x1780, 127, KHMER) \
|
||||
UNICODE_RANGE(0x1800, 175, MONGOLIAN) \
|
||||
UNICODE_RANGE(0x1900, 79, LIMBU) \
|
||||
UNICODE_RANGE(0x1950, 47, TAI_LE) \
|
||||
UNICODE_RANGE(0x19e0, 31, KHMER_SYMBOLS) \
|
||||
UNICODE_RANGE(0x1d00, 127, PHONETIC_EXTENSIONS) \
|
||||
UNICODE_RANGE(0x1e00, 255, LATIN_EXTENDED_ADDITIONAL) \
|
||||
UNICODE_RANGE(0x1f00, 255, GREEK_EXTENDED) \
|
||||
UNICODE_RANGE(0x2000, 111, GENERAL_PUNCTUATION) \
|
||||
UNICODE_RANGE(0x2070, 47, SUPERSCRIPTS_AND_SUBSCRIPTS) \
|
||||
UNICODE_RANGE(0x20a0, 47, CURRENCY_SYMBOLS) \
|
||||
UNICODE_RANGE(0x20d0, 47, COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS) \
|
||||
UNICODE_RANGE(0x2100, 79, LETTERLIKE_SYMBOLS) \
|
||||
UNICODE_RANGE(0x2150, 63, NUMBER_FORMS) \
|
||||
UNICODE_RANGE(0x2190, 111, ARROWS) \
|
||||
UNICODE_RANGE(0x2200, 255, MATHEMATICAL_OPERATORS) \
|
||||
UNICODE_RANGE(0x2300, 255, MISCELLANEOUS_TECHNICAL) \
|
||||
UNICODE_RANGE(0x2400, 63, CONTROL_PICTURES) \
|
||||
UNICODE_RANGE(0x2440, 31, OPTICAL_CHARACTER_RECOGNITION) \
|
||||
UNICODE_RANGE(0x2460, 159, ENCLOSED_ALPHANUMERICS) \
|
||||
UNICODE_RANGE(0x2500, 127, BOX_DRAWING) \
|
||||
UNICODE_RANGE(0x2580, 31, BLOCK_ELEMENTS) \
|
||||
UNICODE_RANGE(0x25a0, 95, GEOMETRIC_SHAPES) \
|
||||
UNICODE_RANGE(0x2600, 255, MISCELLANEOUS_SYMBOLS) \
|
||||
UNICODE_RANGE(0x2700, 191, DINGBATS) \
|
||||
UNICODE_RANGE(0x27c0, 47, MISCELLANEOUS_MATHEMATICAL_SYMBOLS_A) \
|
||||
UNICODE_RANGE(0x27f0, 15, SUPPLEMENTAL_ARROWS_A) \
|
||||
UNICODE_RANGE(0x2800, 255, BRAILLE_PATTERNS) \
|
||||
UNICODE_RANGE(0x2900, 127, SUPPLEMENTAL_ARROWS_B) \
|
||||
UNICODE_RANGE(0x2980, 127, MISCELLANEOUS_MATHEMATICAL_SYMBOLS_B) \
|
||||
UNICODE_RANGE(0x2a00, 255, SUPPLEMENTAL_MATHEMATICAL_OPERATORS) \
|
||||
UNICODE_RANGE(0x2b00, 255, MISCELLANEOUS_SYMBOLS_AND_ARROWS) \
|
||||
UNICODE_RANGE(0x2e80, 127, CJK_RADICALS_SUPPLEMENT) \
|
||||
UNICODE_RANGE(0x2f00, 223, KANGXI_RADICALS) \
|
||||
UNICODE_RANGE(0x2ff0, 15, IDEOGRAPHIC_DESCRIPTION_CHARACTERS) \
|
||||
UNICODE_RANGE(0x3000, 63, CJK_SYMBOLS_AND_PUNCTUATION) \
|
||||
UNICODE_RANGE(0x3040, 95, HIRAGANA) \
|
||||
UNICODE_RANGE(0x30a0, 95, KATAKANA) \
|
||||
UNICODE_RANGE(0x3100, 47, BOPOMOFO) \
|
||||
UNICODE_RANGE(0x3130, 95, HANGUL_COMPATIBILITY_JAMO) \
|
||||
UNICODE_RANGE(0x3190, 15, KANBUN_KUNTEN) \
|
||||
UNICODE_RANGE(0x31a0, 31, BOPOMOFO_EXTENDED) \
|
||||
UNICODE_RANGE(0x31f0, 15, KATAKANA_PHONETIC_EXTENSIONS) \
|
||||
UNICODE_RANGE(0x3200, 255, ENCLOSED_CJK_LETTERS_AND_MONTHS) \
|
||||
UNICODE_RANGE(0x3300, 255, CJK_COMPATIBILITY) \
|
||||
UNICODE_RANGE(0x3400, 6591, CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A) \
|
||||
UNICODE_RANGE(0x4dc0, 63, YIJING_HEXAGRAM_SYMBOLS) \
|
||||
UNICODE_RANGE(0x4e00, 20911, CJK_UNIFIED_IDEOGRAPHS) \
|
||||
UNICODE_RANGE(0xa000, 1167, YI_SYLLABLES) \
|
||||
UNICODE_RANGE(0xa490, 63, YI_RADICALS) \
|
||||
UNICODE_RANGE(0xac00, 11183, HANGUL_SYLLABLES) \
|
||||
UNICODE_RANGE(0xd800, 1023, HIGH_SURROGATE_AREA) \
|
||||
UNICODE_RANGE(0xdc00, 1023, LOW_SURROGATE_AREA) \
|
||||
UNICODE_RANGE(0xe000, 6399, PRIVATE_USE_AREA) \
|
||||
UNICODE_RANGE(0xf900, 511, CJK_COMPATIBILITY_IDEOGRAPHS) \
|
||||
UNICODE_RANGE(0xfb00, 79, ALPHABETIC_PRESENTATION_FORMS) \
|
||||
UNICODE_RANGE(0xfb50, 687, ARABIC_PRESENTATION_FORMS_A) \
|
||||
UNICODE_RANGE(0xfe00, 15, VARIATION_SELECTORS) \
|
||||
UNICODE_RANGE(0xfe20, 15, COMBINING_HALF_MARKS) \
|
||||
UNICODE_RANGE(0xfe30, 31, CJK_COMPATIBILITY_FORMS) \
|
||||
UNICODE_RANGE(0xfe50, 31, SMALL_FORM_VARIANTS) \
|
||||
UNICODE_RANGE(0xfe70, 143, ARABIC_PRESENTATION_FORMS_B) \
|
||||
UNICODE_RANGE(0xff00, 239, HALFWIDTH_AND_FULLWIDTH_FORMS) \
|
||||
UNICODE_RANGE(0xfff0, 15, SPECIALS) \
|
||||
UNICODE_RANGE(0x10000, 127, LINEAR_B_SYLLABARY) \
|
||||
UNICODE_RANGE(0x10080, 127, LINEAR_B_IDEOGRAMS) \
|
||||
UNICODE_RANGE(0x10100, 63, AEGEAN_NUMBERS) \
|
||||
UNICODE_RANGE(0x10300, 47, OLD_ITALIC) \
|
||||
UNICODE_RANGE(0x10330, 31, GOTHIC) \
|
||||
UNICODE_RANGE(0x10380, 31, UGARITIC) \
|
||||
UNICODE_RANGE(0x10400, 79, DESERET) \
|
||||
UNICODE_RANGE(0x10450, 47, SHAVIAN) \
|
||||
UNICODE_RANGE(0x10480, 47, OSMANYA) \
|
||||
UNICODE_RANGE(0x10800, 63, CYPRIOT_SYLLABARY) \
|
||||
UNICODE_RANGE(0x1d000, 255, BYZANTINE_MUSICAL_SYMBOLS) \
|
||||
UNICODE_RANGE(0x1d100, 255, MUSICAL_SYMBOLS) \
|
||||
UNICODE_RANGE(0x1d300, 95, TAI_XUAN_JING_SYMBOLS) \
|
||||
UNICODE_RANGE(0x1d400, 1023, MATHEMATICAL_ALPHANUMERIC_SYMBOLS) \
|
||||
UNICODE_RANGE(0x20000, 42719, CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B) \
|
||||
UNICODE_RANGE(0x2f800, 543, CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT) \
|
||||
UNICODE_RANGE(0xe0000, 127, TAGS) \
|
||||
UNICODE_RANGE(0xe0100, 239, VARIATION_SELECTORS_SUPPLEMENT) \
|
||||
UNICODE_RANGE(0xf0000, 65533, SUPPLEMENTARY_PRIVATE_USE_AREA_A) \
|
||||
UNICODE_RANGE(0x100000, 65533, SUPPLEMENTARY_PRIVATE_USE_AREA_B)
|
||||
|
||||
|
||||
#define UNICODE_RANGE(start, count, name) \
|
||||
extern const unicode_range _cat2_(UNICODE_RANGE_, name);
|
||||
UNICODE_RANGES
|
||||
#undef UNICODE_RANGE
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif //__UTF8_H_
|
|
@ -0,0 +1,115 @@
|
|||
|
||||
|
||||
Shortlist
|
||||
---------
|
||||
[x] let pass flat args in ui_size_push() and ui_box_set_size()
|
||||
[>>] separate style stacks
|
||||
[>] margins? as part of size, or different styling stack?
|
||||
[>] animation time stack
|
||||
[ ] Let build code set target style directly, and animate from current to target
|
||||
[ ] filter styles stack by tag
|
||||
[ ] image backgrounds/gradients?
|
||||
[ ] animating open/close widgets?
|
||||
|
||||
|
||||
Canvas Drawing
|
||||
--------------
|
||||
[.] Correctly handle resizing / viewport
|
||||
[x] associate surfaces with mp_windows
|
||||
[x] window resize resizes surface. Surface always renders to whole mp_window
|
||||
[x] Add ability to create sub-views, and create surfaces for these
|
||||
- window comes with a main view, so we can get a surface for the whole window
|
||||
[>] Clean native mp_window_data struct (don't need to cache a lot of stuff here)
|
||||
|
||||
[.] Add images bliting
|
||||
[x] Clean, rename uv vs texUV stuff
|
||||
[>] Destroy stuff
|
||||
[>] More unified handle system
|
||||
[.] Rounded images (sortof)
|
||||
[ ] path clipped images
|
||||
|
||||
[ ] Add color gradients?
|
||||
[ ] Make canvas implicit?
|
||||
[/] Handle based error signaling
|
||||
[/] Allow polling events in main thread, and updating/rendering in background thread.
|
||||
|
||||
[x] font metrics shouldn't depend on surface, & font
|
||||
shouldn't really depend on canvas either???
|
||||
> meaning we should be able to pass fonts without canvas in ui
|
||||
> and only pass canvas when drawing at the end...
|
||||
|
||||
UI
|
||||
--
|
||||
[x] Make gui context implicit?
|
||||
[x] Uniform ui_box struct + cache widgets
|
||||
[x] Prune unused boxes
|
||||
[.] Layout boxes
|
||||
[x] basic two pass layout
|
||||
[x] Layout from start or end
|
||||
[>] Add overflow flags to layout, & solve conflicts
|
||||
|
||||
[x] Temporarily push transform and text flip when rendering UI, so that the coord system is y down, origin at top left
|
||||
[x] Canvas render the same size on a high-dpi surface
|
||||
> it works with abstract 'pixel' units, which are transformed to pixels in the shader, according to backing store scaling
|
||||
|
||||
[.] Style struct and style stack
|
||||
[ ] Maybe use individual stack for different style attributes
|
||||
[x] Pass initial style in ui_begin_frame()
|
||||
|
||||
[.] Draw boxes
|
||||
[x] use flags to enable/disable drawing each feature
|
||||
[ ] active/hovered transitions
|
||||
|
||||
[x] Change input state handling a move it to app layer
|
||||
[x] Compute signals for ui_box
|
||||
[x] Use ui_size_push() and pass axis instead of ui_width/height_push()
|
||||
[>] Use value is ui_size as margin when kind == text or == children?
|
||||
|
||||
[ ] Allow animating sizes according to hot/active?
|
||||
|
||||
[ ] Basic helpers
|
||||
[.] button
|
||||
[.] slider (or rather, scroll bar)
|
||||
[.] simple spacers
|
||||
[ ] have a flag for non-cached stuff
|
||||
[.] scrolling panel
|
||||
[ ] Allow/disallow scrolling in x/y
|
||||
[ ] Scroll with mousewheel
|
||||
[/] add margins to scrollbars (disallow scrollbars crossing)
|
||||
|
||||
[?] Maybe let builder code handle "active"/"hot" state, since it
|
||||
depends on the widgets
|
||||
[ ] On the other hand, this state must be set before layouting, in
|
||||
particular font/fontSize -> maybe do a pass for static layout,
|
||||
instead of doing it in box creation...
|
||||
[ ] this way we can compute styling after user has set active/hot, but before layout
|
||||
> Maybe just let user set style selector, and provide persistent state bits that can
|
||||
> be used in any way? (to replace eg active/hot?) or perhaps not needed if we have just
|
||||
> 'dragging' state
|
||||
|
||||
|
||||
[x] Mask mouse outside of parent rects -> maintain clip stack and clip mouse against it
|
||||
[x] Mask mouse below panels/other widgets
|
||||
[x] popups and tooltips
|
||||
[x] allow pushing/popping boxes irrespective of parent/child relation
|
||||
[x] ui_begin_frame() prepares two containers, user ui goes in the first one
|
||||
[x] tooltips and menus go to the second one
|
||||
[x] Add menus
|
||||
[ ] line editing widget
|
||||
|
||||
Misc
|
||||
----
|
||||
[x] Split metal surface and metal painter (but put them in the same compilation unit?)
|
||||
[x] Have only one rect struct
|
||||
[x] Shorten mp_string to str8
|
||||
|
||||
[ ] Better/Simpler time API
|
||||
|
||||
[/] Frame throttling
|
||||
[x] For now, we always wait on vblank during mg_surface_present(), regardless of target fps
|
||||
[ ] Then actually get the correct display interval from the surface's current monitor
|
||||
[ ] Allow waiting for more display interval than one? (ie allow throttling at 30fps for a 60fps display)
|
||||
|
||||
[/] split osx_app and move all platform independant stuff outside
|
||||
|
||||
[ ] Sort out mg_matrix_push/pop() -> transform vs. set...
|
Loading…
Reference in New Issue