re-working graphics surfaces

This commit is contained in:
Martin Fouilleul 2022-12-26 18:33:31 +01:00
parent db5b4966e9
commit 9bf62d2218
17 changed files with 382 additions and 171 deletions

View File

@ -3,9 +3,10 @@
BINDIR=../../bin
RESDIR=../../resources
SRCDIR=../../src
EXTDIR=../../ext
INCLUDES="-I$SRCDIR -I$SRCDIR/util -I$SRCDIR/platform -I$SRCDIR/app"
LIBS="-L$BINDIR -lmilepost -framework Carbon -framework Cocoa -framework Metal -framework QuartzCore"
FLAGS="-mmacos-version-min=10.15.4 -DDEBUG -DLOG_COMPILE_DEBUG"
clang -g $FLAGS $LIBS $INCLUDES -o test main.c
clang -g $FLAGS -Wl,-dead_strip $LIBS $INCLUDES -o test main.c

14
examples/triangleMetal/build.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
BINDIR=../../bin
RESDIR=../../resources
SRCDIR=../../src
INCLUDES="-I$SRCDIR -I$SRCDIR/util -I$SRCDIR/platform -I$SRCDIR/app"
LIBS="-L$BINDIR -lmilepost -framework Carbon -framework Cocoa -framework Metal -framework QuartzCore"
FLAGS="-mmacos-version-min=10.15.4 -DDEBUG -DLOG_COMPILE_DEBUG -Wl,-dead_strip"
xcrun -sdk macosx metal -c -o shader.air shader.metal
xcrun -sdk macosx metallib -o shader.metallib shader.air
clang -g $FLAGS $LIBS $INCLUDES -o test main.m

View File

@ -0,0 +1,189 @@
/************************************************************//**
*
* @file: main.cpp
* @author: Martin Fouilleul
* @date: 30/07/2022
* @revision:
*
*****************************************************************/
#include<stdlib.h>
#include<string.h>
#define _USE_MATH_DEFINES //NOTE: necessary for MSVC
#include<math.h>
#include"milepost.h"
#include"metal_surface.h"
#define LOG_SUBSYSTEM "Main"
#import<Metal/Metal.h>
#import<QuartzCore/CAMetalLayer.h>
#include"vertex.h"
static const my_vertex triangle[3] = {{{250, -250},{1, 0, 0, 1}},
{{-250, -250},{0, 1, 0, 1}},
{{0, 250},{0, 0, 1, 1}}};
int main()
{
LogLevel(LOG_LEVEL_DEBUG);
mp_init();
mp_rect rect = {.x = 100, .y = 100, .w = 800, .h = 600};
mp_window window = mp_window_create(rect, "test", 0);
//NOTE: create surface
mg_init();
mg_surface surface = mg_surface_create_for_window(window, MG_BACKEND_METAL);
//NOTE(martin): load the library
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
const char* shaderPath = "shader.metallib";
NSString* metalFileName = [[NSString alloc] initWithCString: shaderPath encoding: NSUTF8StringEncoding];
NSError* err = 0;
id<MTLLibrary> library = [device newLibraryWithFile: metalFileName error:&err];
if(err != nil)
{
const char* errStr = [[err localizedDescription] UTF8String];
printf("error : %s\n", errStr);
return(-1);
}
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"VertexShader"];
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"FragmentShader"];
//NOTE(martin): create a render pipeline
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"My simple pipeline";
pipelineStateDescriptor.vertexFunction = vertexFunction;
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
CAMetalLayer* layer = mg_metal_surface_layer(surface);
pipelineStateDescriptor.colorAttachments[0].pixelFormat = layer.pixelFormat;
id<MTLRenderPipelineState> pipelineState = [device newRenderPipelineStateWithDescriptor: pipelineStateDescriptor error:&err];
if(err != nil)
{
const char* errStr = [[err localizedDescription] UTF8String];
printf("error : %s\n", errStr);
return(-1);
}
// start app
mp_window_bring_to_front(window);
mp_window_focus(window);
while(!mp_should_quit())
{
mp_pump_events(0);
mp_event event = {0};
while(mp_next_event(&event))
{
switch(event.type)
{
case MP_EVENT_WINDOW_CLOSE:
{
mp_request_quit();
} break;
case MP_EVENT_WINDOW_RESIZE:
{
printf("resized, rect = {%f, %f, %f, %f}\n",
event.frame.rect.x,
event.frame.rect.y,
event.frame.rect.w,
event.frame.rect.h);
} break;
case MP_EVENT_WINDOW_MOVE:
{
printf("moved, rect = {%f, %f, %f, %f}\n",
event.frame.rect.x,
event.frame.rect.y,
event.frame.rect.w,
event.frame.rect.h);
} break;
case MP_EVENT_MOUSE_MOVE:
{
printf("mouse moved, pos = {%f, %f}, delta = {%f, %f}\n",
event.move.x,
event.move.y,
event.move.deltaX,
event.move.deltaY);
} break;
case MP_EVENT_MOUSE_WHEEL:
{
printf("mouse wheel, delta = {%f, %f}\n",
event.move.deltaX,
event.move.deltaY);
} break;
case MP_EVENT_MOUSE_ENTER:
{
printf("mouse enter\n");
} break;
case MP_EVENT_MOUSE_LEAVE:
{
printf("mouse leave\n");
} break;
case MP_EVENT_MOUSE_BUTTON:
{
printf("mouse button %i: %i\n",
event.key.code,
event.key.action == MP_KEY_PRESS ? 1 : 0);
} break;
case MP_EVENT_KEYBOARD_KEY:
{
printf("key %i: %s\n",
event.key.code,
event.key.action == MP_KEY_PRESS ? "press" : (event.key.action == MP_KEY_RELEASE ? "release" : "repeat"));
} break;
case MP_EVENT_KEYBOARD_CHAR:
{
printf("entered char %s\n", event.character.sequence);
} break;
default:
break;
}
}
vector_uint2 viewportSize;
viewportSize.x = 800;
viewportSize.y = 600;
mg_surface_prepare(surface);
id<CAMetalDrawable> drawable = mg_metal_surface_drawable(surface);
id<MTLCommandBuffer> commandBuffer = mg_metal_surface_command_buffer(surface);
MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
renderPassDescriptor.colorAttachments[0].texture = drawable.texture;
renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
id<MTLRenderCommandEncoder> encoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
//Set the pipeline state
[encoder setRenderPipelineState:pipelineState];
//Send data to the shader and add draw call
[encoder setVertexBytes: triangle length:sizeof(triangle) atIndex: vertexInputIndexVertices];
[encoder setVertexBytes: &viewportSize length:sizeof(viewportSize) atIndex: vertexInputIndexViewportSize];
[encoder drawPrimitives: MTLPrimitiveTypeTriangle vertexStart: 0 vertexCount: 3];
[encoder endEncoding];
mg_surface_present(surface);
}
mp_terminate();
return(0);
}

View File

@ -0,0 +1,36 @@
#include<metal_stdlib>
#include<simd/simd.h>
using namespace metal;
#import "vertex.h"
typedef unsigned int uint;
typedef struct
{
float4 clipSpacePos [[position]];
float4 color;
} rasterizer_data;
vertex rasterizer_data VertexShader(uint vertexID [[vertex_id]],
constant my_vertex* vertices [[buffer(vertexInputIndexVertices)]],
constant vector_uint2* viewportSizePointer [[buffer(vertexInputIndexViewportSize)]])
{
float2 pixelPos = vertices[vertexID].pos.xy;
float2 viewportSize = vector_float2(*viewportSizePointer);
rasterizer_data out;
out.clipSpacePos = vector_float4(0, 0, 0, 1);
out.clipSpacePos.xy = pixelPos/(viewportSize/2);
out.color = vertices[vertexID].col;
return(out);
}
fragment float4 FragmentShader(rasterizer_data in [[stage_in]])
{
return(in.color);
}

View File

@ -0,0 +1,17 @@
#ifndef __VERTEX_H__
#define __VERTEX_H__
#include<simd/simd.h>
typedef struct
{
vector_float2 pos;
vector_float4 col;
} my_vertex;
typedef enum { vertexInputIndexVertices = 0,
vertexInputIndexViewportSize = 1 } vertexInputIndex;
#endif //__VERTEX_H__

View File

@ -537,15 +537,13 @@ mg_image_data* mg_image_ptr_from_handle(mg_canvas_data* context, mg_image handle
//---------------------------------------------------------------
#ifdef MG_IMPLEMENTS_BACKEND_METAL
//NOTE: function is defined in metal_backend.mm
mg_surface mg_metal_surface_create_for_window(mp_window window);
mg_surface mg_metal_surface_create_for_view(mp_view view);
#endif //MG_IMPLEMENTS_BACKEND_METAL
#include"metal_surface.h"
#endif
#ifdef MG_IMPLEMENTS_BACKEND_GLES
mg_surface mg_gles_surface_create_offscreen(u32 width, u32 height);
mg_surface_server mg_gles_surface_create_server(mg_surface_info* surface);
#endif //MG_IMPLEMENTS_BACKEND_GLES
#endif
#ifdef MG_IMPLEMENTS_BACKEND_GL
mg_surface mg_gl_surface_create_for_window(mp_window window);
@ -582,29 +580,6 @@ mg_surface mg_surface_create_for_window(mp_window window, mg_backend_id backend)
return(surface);
}
mg_surface mg_surface_create_for_view(mp_view view, mg_backend_id backend)
{
DEBUG_ASSERT(__mgInfo.init);
mg_surface surface = mg_surface_nil();
switch(backend)
{
#ifdef MG_IMPLEMENTS_BACKEND_METAL
case MG_BACKEND_METAL:
surface = mg_metal_surface_create_for_view(view);
break;
#endif
//...
default:
break;
}
return(surface);
}
mg_surface mg_surface_create_offscreen(mg_backend_id backend, u32 width, u32 height)
{
DEBUG_ASSERT(__mgInfo.init);
@ -795,17 +770,6 @@ void mg_surface_client_destroy(mg_surface_client handle)
}
}
void mg_surface_client_attach_to_view(mg_surface_client client, mp_view view)
{
mg_surface_client_info* clientInfo = mg_surface_client_ptr_from_handle(client);
if(clientInfo)
{
clientInfo->attachment = view;
clientInfo->attach(clientInfo);
}
}
void mg_surface_client_detach(mg_surface_client client)
{
mg_surface_client_info* clientInfo = mg_surface_client_ptr_from_handle(client);

View File

@ -33,7 +33,6 @@ void mg_init();
mg_surface mg_surface_nil();
bool mg_surface_is_nil(mg_surface surface);
mg_surface mg_surface_create_for_window(mp_window window, mg_backend_id backend);
mg_surface mg_surface_create_for_view(mp_view view, mg_backend_id backend);
mg_surface mg_surface_create_offscreen(mg_backend_id backend, u32 width, u32 height);
void mg_surface_destroy(mg_surface surface);
@ -41,10 +40,13 @@ void* mg_surface_get_os_resource(mg_surface surface);
void mg_surface_prepare(mg_surface surface);
void mg_surface_present(mg_surface surface);
void mg_surface_resize(mg_surface surface, int width, int height);
void mg_surface_set_frame(mg_surface surface, mp_rect frame);
void mg_surface_set_hidden(mg_surface surface, bool hidden);
vec2 mg_surface_size(mg_surface surface);
mp_rect mg_surface_frame(mg_surface surface);
//------------------------------------------------------------------------------------------
//NOTE(martin): graphics surface sharing
@ -61,8 +63,6 @@ mg_surface_server_id mg_surface_server_get_id(mg_surface_server server);
mg_surface_client mg_surface_client_create(mg_surface_server_id id);
void mg_surface_client_destroy(mg_surface_client client);
void mg_surface_client_attach_to_view(mg_surface_client client, mp_view view);
void mg_surface_client_detach(mg_surface_client client);
//------------------------------------------------------------------------------------------
//NOTE(martin): canvas drawing structs

View File

@ -75,8 +75,6 @@ typedef struct mg_surface_client_info
mg_surface_client_attach_proc attach;
mg_surface_client_detach_proc detach;
mp_view attachment;
} mg_surface_client_info;
mg_surface_client mg_surface_client_alloc_handle(mg_surface_client_info* client);

24
src/metal_surface.h Normal file
View File

@ -0,0 +1,24 @@
/************************************************************//**
*
* @file: metal_surface.h
* @author: Martin Fouilleul
* @date: 25/12/2022
* @revision:
*
*****************************************************************/
#ifndef __METAL_SURFACE_H_
#define __METAL_SURFACE_H_
#ifdef __OBJC__
#import<Metal/Metal.h>
#endif
mg_surface mg_metal_surface_create_for_window(mp_window window);
void* mg_metal_surface_render_encoder(mg_surface surface);
void* mg_metal_surface_compute_encoder(mg_surface surface);
void* mg_metal_surface_layer(mg_surface surface);
void* mg_metal_surface_drawable(mg_surface surface);
void* mg_metal_surface_command_buffer(mg_surface surface);
#endif //__METAL_SURFACE_H_

View File

@ -154,7 +154,6 @@ void mg_metal_surface_set_hidden(mg_surface_info* interface, bool hidden)
}
}
vec2 mg_metal_surface_get_size(mg_surface_info* interface)
{
mg_metal_surface* surface = (mg_metal_surface*)interface;
@ -175,10 +174,10 @@ void* mg_metal_surface_get_os_resource(mg_surface_info* interface)
static const f32 MG_METAL_SURFACE_CONTENTS_SCALING = 2;
mg_surface mg_metal_surface_create_for_view(mp_view view)
mg_surface mg_metal_surface_create_for_window(mp_window window)
{
mp_view_data* viewData = mp_view_ptr_from_handle(view);
if(!viewData)
mp_window_data* windowData = mp_window_ptr_from_handle(window);
if(!windowData)
{
return(mg_surface_nil());
}
@ -198,6 +197,12 @@ mg_surface mg_metal_surface_create_for_view(mp_view view)
@autoreleasepool
{
NSRect frame = [[windowData->osx.nsWindow contentView] frame];
surface->view = [[NSView alloc] initWithFrame: frame];
[surface->view setWantsLayer:YES];
[[windowData->osx.nsWindow contentView] addSubview: surface->view];
surface->drawableSemaphore = dispatch_semaphore_create(MP_METAL_MAX_DRAWABLES_IN_FLIGHT);
//-----------------------------------------------------------
@ -211,8 +216,6 @@ mg_surface mg_metal_surface_create_for_view(mp_view view)
surface->metalLayer.device = surface->device;
surface->view = viewData->nsView;
//-----------------------------------------------------------
//NOTE(martin): set the size and scaling
//-----------------------------------------------------------
@ -244,23 +247,51 @@ mg_surface mg_metal_surface_create_for_view(mp_view view)
}
mg_surface handle = mg_surface_alloc_handle((mg_surface_info*)surface);
viewData->surface = handle;
return(handle);
}
}
mg_surface mg_metal_surface_create_for_window(mp_window window)
void* mg_metal_surface_layer(mg_surface surface)
{
mp_window_data* windowData = mp_window_ptr_from_handle(window);
if(!windowData)
mg_surface_info* surfaceData = mg_surface_ptr_from_handle(surface);
if(surfaceData && surfaceData->backend == MG_BACKEND_METAL)
{
return(mg_surface_nil());
mg_metal_surface* metalSurface = (mg_metal_surface*)surfaceData;
return(metalSurface->metalLayer);
}
else
{
return(mg_metal_surface_create_for_view(windowData->osx.mainView));
return(nil);
}
}
void* mg_metal_surface_drawable(mg_surface surface)
{
mg_surface_info* surfaceData = mg_surface_ptr_from_handle(surface);
if(surfaceData && surfaceData->backend == MG_BACKEND_METAL)
{
mg_metal_surface* metalSurface = (mg_metal_surface*)surfaceData;
return(metalSurface->drawable);
}
else
{
return(nil);
}
}
void* mg_metal_surface_command_buffer(mg_surface surface)
{
mg_surface_info* surfaceData = mg_surface_ptr_from_handle(surface);
if(surfaceData && surfaceData->backend == MG_BACKEND_METAL)
{
mg_metal_surface* metalSurface = (mg_metal_surface*)surfaceData;
return(metalSurface->commandBuffer);
}
else
{
return(nil);
}
}
#undef LOG_SUBSYSTEM

View File

@ -16,4 +16,4 @@
#include"osx_gles_surface.m"
#pragma clang diagnostic pop
#include"osx_surface_client.m"
//#include"osx_surface_client.m"

View File

@ -22,7 +22,6 @@ extern "C" {
//--------------------------------------------------------------------
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,
@ -320,16 +319,6 @@ void mp_window_center(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);
//--------------------------------------------------------------------
// 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);
//--------------------------------------------------------------------
// Input state polling
//--------------------------------------------------------------------

View File

@ -30,22 +30,10 @@ typedef struct osx_window_data
NSView* nsView;
NSObject* nsWindowDelegate;
mp_view mainView;
} osx_window_data;
#define MP_PLATFORM_WINDOW_DATA osx_window_data osx;
typedef struct mp_view_data
{
list_elt freeListElt;
u32 generation;
mp_window window;
NSView* nsView;
mg_surface surface;
} mp_view_data;
const u32 MP_APP_MAX_VIEWS = 128;
typedef struct osx_app_data
@ -57,9 +45,6 @@ typedef struct osx_app_data
void* kbLayoutUnicodeData;
id kbLayoutListener;
list_info viewFreeList;
mp_view_data viewPool[MP_APP_MAX_VIEWS];
} osx_app_data;
#define MP_PLATFORM_APP_DATA osx_app_data osx;

View File

@ -90,64 +90,6 @@ static u32 mp_osx_get_window_style_mask(mp_window_style style)
}
//---------------------------------------------------------------
// App struct and utility functions
//---------------------------------------------------------------
/////////////////////////////////////////////////////////////////////////////////////////////////////////
void mp_init_view_handles()
{
ListInit(&__mpApp.osx.viewFreeList);
for(int i=0; i<MP_APP_MAX_VIEWS; i++)
{
__mpApp.osx.viewPool[i].generation = 1;
ListAppend(&__mpApp.osx.viewFreeList, &__mpApp.osx.viewPool[i].freeListElt);
}
}
mp_view_data* mp_view_alloc()
{
return(ListPopEntry(&__mpApp.osx.viewFreeList, mp_view_data, freeListElt));
}
mp_view_data* mp_view_ptr_from_handle(mp_view handle)
{
u32 index = handle.h>>32;
u32 generation = handle.h & 0xffffffff;
if(index >= MP_APP_MAX_VIEWS)
{
return(0);
}
mp_view_data* view = &__mpApp.osx.viewPool[index];
if(view->generation != generation)
{
return(0);
}
else
{
return(view);
}
}
mp_view mp_view_handle_from_ptr(mp_view_data* view)
{
DEBUG_ASSERT( (view - __mpApp.osx.viewPool) >= 0
&& (view - __mpApp.osx.viewPool) < MP_APP_MAX_VIEWS);
u64 h = ((u64)(view - __mpApp.osx.viewPool))<<32
| ((u64)view->generation);
return((mp_view){h});
}
void mp_view_recycle_ptr(mp_view_data* view)
{
view->generation++;
ListPush(&__mpApp.osx.viewFreeList, &view->freeListElt);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
static void mp_init_osx_keys()
{
@ -719,10 +661,6 @@ static void mp_update_key_mods(mp_key_mods mods)
event.frame.rect.w = contentRect.size.width;
event.frame.rect.h = contentRect.size.height;
mp_rect viewFrame = {0, 0, contentRect.size.width, contentRect.size.height};
mp_view_set_frame(mpWindow->osx.mainView, viewFrame);
if(__mpApp.liveResizeCallback)
{
__mpApp.liveResizeCallback(event, __mpApp.liveResizeData);
@ -1269,7 +1207,6 @@ void mp_init()
LOG_MESSAGE("init handles\n");
mp_init_window_handles();
mp_init_view_handles();
LOG_MESSAGE("init event queue\n");
ringbuffer_init(&__mpApp.eventQueue, 16);
@ -1508,9 +1445,6 @@ mp_window mp_window_create(mp_rect contentRect, const char* title, mp_window_sty
mp_window windowHandle = mp_window_handle_from_ptr(window);
mp_rect mainViewFrame = {0, 0, contentRect.w, contentRect.h};
window->osx.mainView = mp_view_create(windowHandle, mainViewFrame);
return(windowHandle);
}//autoreleasepool
}
@ -1780,17 +1714,7 @@ void mp_window_set_content_size(mp_window window, int width, int height)
//--------------------------------------------------------------------
// view management
//--------------------------------------------------------------------
mp_view mp_view_nil()
{
return((mp_view){0});
}
bool mp_view_is_nil(mp_view view)
{
return(!view.h);
}
/*
mp_view mp_view_create(mp_window windowHandle, mp_rect frame)
{@autoreleasepool{
mp_window_data* window = mp_window_ptr_from_handle(windowHandle);
@ -1853,7 +1777,7 @@ void mp_view_set_frame(mp_view viewHandle, mp_rect frame)
mg_surface_resize(view->surface, frame.w, frame.h);
}
}
*/
//--------------------------------------------------------------------
// Main loop throttle
//--------------------------------------------------------------------

View File

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