initial commit

This commit is contained in:
Martin Fouilleul 2022-08-14 18:19:40 +02:00
commit 39cfa35bfd
53 changed files with 26626 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.DS_Store
bin/*
*.metallib

81
build.sh Executable file
View File

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

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

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

279
examples/test_app/main.c Normal file
View File

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

BIN
resources/Andale Mono.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resources/Courier.ttf Normal file

Binary file not shown.

Binary file not shown.

4100
src/graphics.c Normal file

File diff suppressed because it is too large Load Diff

228
src/graphics.h Normal file
View File

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

90
src/graphics_internal.h Normal file
View File

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

419
src/metal_painter.mm Normal file
View File

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

49
src/metal_shader.h Normal file
View File

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

320
src/metal_shader.metal Normal file
View File

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

231
src/metal_surface.mm Normal file
View File

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

29
src/milepost.c Normal file
View File

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

31
src/milepost.h Normal file
View File

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

12
src/milepost.mm Normal file
View File

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

413
src/mp_app.h Normal file
View File

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

58
src/osx_app.h Normal file
View File

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

2401
src/osx_app.mm Normal file

File diff suppressed because it is too large Load Diff

193
src/platform/linux_clock.c Normal file
View File

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

176
src/platform/osx_clock.c Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

515
src/platform/posix_socket.c Normal file
View File

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

241
src/platform/posix_thread.c Normal file
View File

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

View File

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

60
src/platform/unix_rng.c Normal file
View File

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

7440
src/stb_image.h Normal file

File diff suppressed because it is too large Load Diff

5011
src/stb_truetype.h Normal file

File diff suppressed because it is too large Load Diff

1332
src/ui.c Normal file

File diff suppressed because it is too large Load Diff

212
src/ui.h Normal file
View File

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

123
src/util/debug_log.c Normal file
View File

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

97
src/util/debug_log.h Normal file
View File

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

124
src/util/hash.c Normal file
View File

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

29
src/util/hash.h Normal file
View File

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

390
src/util/lists.h Normal file
View File

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

129
src/util/macro_helpers.h Normal file
View File

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

129
src/util/memory.c Normal file
View File

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

106
src/util/memory.h Normal file
View File

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

94
src/util/ringbuffer.c Normal file
View File

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

40
src/util/ringbuffer.h Normal file
View File

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

298
src/util/strings.c Normal file
View File

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

96
src/util/strings.h Normal file
View File

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

70
src/util/typedefs.h Normal file
View File

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

283
src/util/utf8.c Normal file
View File

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

201
src/util/utf8.h Normal file
View File

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

115
todo.txt Normal file
View File

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