1348 lines
46 KiB
Objective-C
1348 lines
46 KiB
Objective-C
// Graphics layer abstraction.
|
|
|
|
#ifndef ED_GFX_INCLUDED
|
|
#define ED_GFX_INCLUDED
|
|
#include <stdint.h>
|
|
|
|
#include "ed_array.h"
|
|
#include "string.h"
|
|
|
|
bool keep_running = true;
|
|
|
|
#if defined(__APPLE__)
|
|
#include <AppKit/AppKit.h>
|
|
#include <CoreGraphics/CoreGraphics.h>
|
|
#include <Foundation/Foundation.h>
|
|
#include <Metal/Metal.h>
|
|
#include <QuartzCore/CoreAnimation.h>
|
|
#include <QuartzCore/QuartzCore.h>
|
|
@interface EDGFXView : NSView
|
|
@property NSTrackingArea *tracking_area;
|
|
@end
|
|
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
|
@end
|
|
@interface WindowDelegate : NSObject <NSWindowDelegate>
|
|
@end
|
|
|
|
#define wrapIdArray(T) \
|
|
typedef id<T> _##T; \
|
|
arrayTemplate(_##T);
|
|
|
|
wrapIdArray(MTLRenderPipelineState);
|
|
wrapIdArray(MTLBuffer);
|
|
wrapIdArray(MTLTexture);
|
|
#endif
|
|
|
|
typedef struct {
|
|
float position[4];
|
|
float size[4];
|
|
float border_size[4];
|
|
float color[4];
|
|
} GpuUiRect;
|
|
arrayTemplate(GpuUiRect);
|
|
|
|
typedef struct {
|
|
float atlas_position[2];
|
|
float size[2];
|
|
float position[2];
|
|
float y_offset;
|
|
float _haha_alignment;
|
|
float color[4];
|
|
} GpuGlyph;
|
|
arrayTemplate(GpuGlyph);
|
|
|
|
typedef struct {
|
|
float screen_size[2];
|
|
float font_size[2];
|
|
} GpuUniformParams;
|
|
|
|
#if defined(__APPLE__)
|
|
typedef struct {
|
|
NSApplication *application;
|
|
NSWindow *window;
|
|
EDGFXView *view;
|
|
bool keep_running;
|
|
bool refresh_now;
|
|
|
|
int mouse_x, mouse_y;
|
|
bool mouse_left_down, mouse_right_down;
|
|
|
|
// Metal objects
|
|
id<MTLDevice> device;
|
|
CAMetalLayer *metal_layer;
|
|
id<MTLLibrary> library;
|
|
id<MTLCommandQueue> command_queue;
|
|
|
|
array(_MTLRenderPipelineState) pipelines;
|
|
array(_MTLBuffer) buffers;
|
|
array(_MTLTexture) textures;
|
|
} _metal_gfx_context;
|
|
#elif __linux__
|
|
#include "wayland-crap/xdg-shell.h"
|
|
#include <EGL/egl.h>
|
|
#include <GL/gl.h>
|
|
#include <X11/Xlib.h>
|
|
#include <sys/mman.h>
|
|
#include <syscall.h>
|
|
#include <unistd.h>
|
|
#include <wayland-client.h>
|
|
#include <wayland-egl.h>
|
|
|
|
#include "file_io.h"
|
|
|
|
// And I thought MacOS needed a lot of state to create a window
|
|
arrayTemplate(GLuint);
|
|
typedef struct {
|
|
struct wl_display *display;
|
|
struct wl_registry *registry;
|
|
struct wl_surface *surface;
|
|
struct wl_compositor *compositor;
|
|
struct wl_shm *shared_memory;
|
|
struct wl_shm_pool *shared_memory_pool;
|
|
struct xdg_wm_base *wm_base;
|
|
struct wl_buffer *buffer;
|
|
struct xdg_surface *xdg_surface;
|
|
struct xdg_toplevel *xdg_toplevel;
|
|
struct wl_seat *seat;
|
|
struct wl_pointer *pointer;
|
|
uint8_t *pixels;
|
|
|
|
EGLDisplay egl_display;
|
|
EGLConfig egl_config;
|
|
EGLSurface egl_surface;
|
|
EGLContext egl_context;
|
|
struct wl_egl_window *egl_window;
|
|
|
|
int mouse_x, mouse_y;
|
|
int mouse_left_down, mouse_right_down;
|
|
|
|
GLuint ui_rect_vertex_shader;
|
|
GLuint ui_rect_fragment_shader;
|
|
GLuint text_atlas_vertex_shader;
|
|
GLuint text_atlas_fragment_shader;
|
|
GLuint ui_rect_shader_program;
|
|
GLuint text_atlas_shader_program;
|
|
|
|
array(GLuint) buffers;
|
|
array(GLuint) textures;
|
|
} _opengl_gfx_context_wayland;
|
|
|
|
static void _opengl_gfx_present_wayland(_opengl_gfx_context_wayland *cx);
|
|
|
|
typedef struct {
|
|
Display *display;
|
|
Window window;
|
|
int screen;
|
|
} _opengl_gfx_context_x11;
|
|
#endif
|
|
|
|
typedef void (*_gfx_frame_func)(int mouse_x, int mouse_y, bool mouse_left_down,
|
|
bool mouse_right_down);
|
|
typedef struct {
|
|
#if defined(__APPLE__)
|
|
_metal_gfx_context backend;
|
|
#elif __linux__
|
|
// TODO: be able to use X11 or Wayland at runtime
|
|
_opengl_gfx_context_wayland backend;
|
|
#else
|
|
#error "Unsupported platform"
|
|
#endif
|
|
|
|
uint32_t frame_width;
|
|
uint32_t frame_height;
|
|
_gfx_frame_func frame_func;
|
|
|
|
array(GpuUiRect) gpu_ui_rects;
|
|
array(GpuGlyph) gpu_glyphs;
|
|
array(GpuGlyph) glyph_cache;
|
|
} gfx_context_t;
|
|
static gfx_context_t _gfx_context;
|
|
|
|
#ifdef ED_GFX_IMPLEMENTATION
|
|
|
|
#if defined(__APPLE__)
|
|
static void _metal_gfx_present(_metal_gfx_context *cx);
|
|
static void _metal_gfx_send_events(_metal_gfx_context *cx);
|
|
|
|
void gfx_update_buffer(gfx_context_t *cx, size_t buffer_index, const void *data,
|
|
size_t len);
|
|
|
|
@implementation AppDelegate
|
|
- (NSApplicationTerminateReply)applicationShouldTerminate:
|
|
(NSApplication *)sender {
|
|
keep_running = false;
|
|
|
|
return NSTerminateCancel;
|
|
}
|
|
|
|
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:
|
|
(NSApplication *)sender {
|
|
keep_running = false;
|
|
|
|
return NO;
|
|
}
|
|
@end
|
|
|
|
@implementation WindowDelegate
|
|
- (BOOL)windowShouldClose:(NSApplication *)sender {
|
|
keep_running = false;
|
|
return YES;
|
|
}
|
|
|
|
- (void)windowDidResize:(NSNotification *)notification {
|
|
_gfx_context.frame_width =
|
|
_gfx_context.backend.window.contentView.frame.size.width;
|
|
_gfx_context.frame_height =
|
|
_gfx_context.backend.window.contentView.frame.size.height;
|
|
|
|
CGFloat scale = _gfx_context.backend.metal_layer.contentsScale;
|
|
[_gfx_context.backend.metal_layer
|
|
setDrawableSize:CGSizeMake(_gfx_context.frame_width * scale,
|
|
_gfx_context.frame_height * scale)];
|
|
|
|
_gfx_context.backend.refresh_now = true;
|
|
_gfx_context.backend.view.needsDisplay = true;
|
|
}
|
|
@end
|
|
|
|
@implementation EDGFXView
|
|
- (BOOL)isOpaque {
|
|
return YES;
|
|
}
|
|
- (void)updateLayer {
|
|
_metal_gfx_present(&_gfx_context.backend);
|
|
}
|
|
|
|
- (BOOL)wantsLayer {
|
|
return YES;
|
|
}
|
|
- (BOOL)wantsUpdateLayer {
|
|
return YES;
|
|
}
|
|
- (NSViewLayerContentsRedrawPolicy)layerContentsRedrawPolicy {
|
|
return NSViewLayerContentsRedrawOnSetNeedsDisplay;
|
|
}
|
|
|
|
- (id)initWithFrame:(NSRect)frameRect {
|
|
self = [super initWithFrame:frameRect];
|
|
NSRect rect =
|
|
NSMakeRect(0, 0,
|
|
_gfx_context.frame_width =
|
|
_gfx_context.backend.window.contentView.frame.size.width,
|
|
_gfx_context.frame_height =
|
|
_gfx_context.backend.window.contentView.frame.size.height);
|
|
|
|
self.tracking_area = [[NSTrackingArea alloc]
|
|
initWithRect:rect
|
|
options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |
|
|
NSTrackingActiveInKeyWindow)
|
|
owner:self
|
|
userInfo:nil];
|
|
[self addTrackingArea:self.tracking_area];
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)mouseDown:(NSEvent *)event {
|
|
if ((unsigned long)event.type == NSEventTypeLeftMouseDown) {
|
|
_gfx_context.backend.mouse_left_down = true;
|
|
}
|
|
if (event.type == NSEventTypeRightMouseUp) {
|
|
_gfx_context.backend.mouse_right_down = true;
|
|
}
|
|
|
|
_gfx_context.gpu_glyphs.size = 0;
|
|
_gfx_context.gpu_ui_rects.size = 0;
|
|
_gfx_context.frame_func(_gfx_context.backend.mouse_x,
|
|
_gfx_context.frame_height -
|
|
_gfx_context.backend.mouse_y,
|
|
_gfx_context.backend.mouse_left_down,
|
|
_gfx_context.backend.mouse_right_down);
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
|
|
- (void)mouseUp:(NSEvent *)event {
|
|
if ((unsigned long)event.type == NSEventTypeLeftMouseUp) {
|
|
_gfx_context.backend.mouse_left_down = false;
|
|
}
|
|
if (event.type == NSEventTypeRightMouseDown) {
|
|
_gfx_context.backend.mouse_right_down = false;
|
|
}
|
|
|
|
_gfx_context.gpu_glyphs.size = 0;
|
|
_gfx_context.gpu_ui_rects.size = 0;
|
|
_gfx_context.frame_func(_gfx_context.backend.mouse_x,
|
|
_gfx_context.frame_height -
|
|
_gfx_context.backend.mouse_y,
|
|
_gfx_context.backend.mouse_left_down,
|
|
_gfx_context.backend.mouse_right_down);
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
|
|
- (void)mouseDragged:(NSEvent *)event {
|
|
NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil];
|
|
|
|
_gfx_context.backend.mouse_x = location.x;
|
|
_gfx_context.backend.mouse_y = location.y;
|
|
|
|
_gfx_context.gpu_glyphs.size = 0;
|
|
_gfx_context.gpu_ui_rects.size = 0;
|
|
_gfx_context.frame_func(_gfx_context.backend.mouse_x,
|
|
_gfx_context.frame_height -
|
|
_gfx_context.backend.mouse_y,
|
|
_gfx_context.backend.mouse_left_down,
|
|
_gfx_context.backend.mouse_right_down);
|
|
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
|
|
- (void)mouseMoved:(NSEvent *)event {
|
|
NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil];
|
|
|
|
_gfx_context.backend.mouse_x = location.x;
|
|
_gfx_context.backend.mouse_y = location.y;
|
|
|
|
[self setNeedsDisplay:YES];
|
|
[self displayIfNeeded];
|
|
[_gfx_context.backend.metal_layer setNeedsDisplay];
|
|
[_gfx_context.backend.metal_layer displayIfNeeded];
|
|
}
|
|
|
|
- (void)updateTrackingAreas {
|
|
[self removeTrackingArea:self.tracking_area];
|
|
[self.tracking_area release];
|
|
|
|
NSRect rect =
|
|
NSMakeRect(0, 0,
|
|
_gfx_context.frame_width =
|
|
_gfx_context.backend.window.contentView.frame.size.width,
|
|
_gfx_context.frame_height =
|
|
_gfx_context.backend.window.contentView.frame.size.height);
|
|
|
|
self.tracking_area = [[NSTrackingArea alloc]
|
|
initWithRect:rect
|
|
options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |
|
|
NSTrackingActiveInKeyWindow)
|
|
owner:self
|
|
userInfo:nil];
|
|
[self addTrackingArea:self.tracking_area];
|
|
}
|
|
@end
|
|
|
|
static _metal_gfx_context _metal_gfx_init_context(uint32_t width,
|
|
uint32_t height) {
|
|
NSApplication *application = [NSApplication sharedApplication];
|
|
if (application == NULL) {
|
|
fprintf(stderr, "NSApplication:sharedApplication failed\n");
|
|
exit(1);
|
|
}
|
|
|
|
NSString *title = @"chat - [Slack Sux]";
|
|
|
|
NSRect rect = NSMakeRect(0, 0, width, height);
|
|
NSWindow *window = [[NSWindow alloc]
|
|
initWithContentRect:rect
|
|
styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
|
|
NSWindowStyleMaskMiniaturizable |
|
|
NSWindowStyleMaskResizable
|
|
backing:NSBackingStoreBuffered
|
|
defer:NO];
|
|
EDGFXView *view = [[EDGFXView alloc] initWithFrame:rect];
|
|
[view updateTrackingAreas];
|
|
[window setTitle:title];
|
|
[window setContentView:view];
|
|
[window setDelegate:[[WindowDelegate alloc] init]];
|
|
[window makeKeyAndOrderFront:NULL];
|
|
|
|
// TODO: make this work
|
|
// if (application.mainMenu == NULL) {
|
|
// NSMenu *menu = [[NSMenu alloc] initWithTitle:@"an_editor"];
|
|
// if (menu == NULL) {
|
|
// fprintf(stderr, "failed to create application menu\n");
|
|
// exit(1);
|
|
// }
|
|
// application.mainMenu = menu;
|
|
// }
|
|
[application setDelegate:[[AppDelegate alloc] init]];
|
|
[application setActivationPolicy:NSApplicationActivationPolicyRegular];
|
|
[application setPresentationOptions:NSApplicationPresentationDefault];
|
|
[application finishLaunching];
|
|
|
|
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
|
|
|
|
CAMetalLayer *metal_layer = [CAMetalLayer layer];
|
|
metal_layer.device = device;
|
|
metal_layer.pixelFormat = MTLPixelFormatRGBA8Unorm;
|
|
metal_layer.frame = CGRectMake(0, 0, width, height);
|
|
metal_layer.needsDisplayOnBoundsChange = YES;
|
|
metal_layer.presentsWithTransaction = YES;
|
|
metal_layer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
|
|
|
|
// TODO: set this to the display dpi scale
|
|
metal_layer.contentsScale = 2.0;
|
|
view.wantsLayer = YES;
|
|
[view.layer addSublayer:metal_layer];
|
|
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
|
|
|
|
NSError *libraryError = NULL;
|
|
NSURL *libraryURL = [[NSBundle mainBundle] URLForResource:@"./shaders"
|
|
withExtension:@"metallib"];
|
|
if (libraryURL == NULL) {
|
|
fprintf(stderr, "Couldn't find library file\n");
|
|
exit(1);
|
|
}
|
|
|
|
id<MTLLibrary> library =
|
|
[device newLibraryWithURL:libraryURL error:&libraryError];
|
|
|
|
if (library == NULL) {
|
|
if (libraryError.description != NULL) {
|
|
NSLog(@"Error description: %@\n", libraryError.description);
|
|
}
|
|
|
|
exit(1);
|
|
}
|
|
|
|
id<MTLCommandQueue> command_queue = [device newCommandQueue];
|
|
id<MTLFunction> vertex_func = [library newFunctionWithName:@"vs_main"];
|
|
id<MTLFunction> fragment_func = [library newFunctionWithName:@"fs_main"];
|
|
id<MTLFunction> ui_rect_vertex_func =
|
|
[library newFunctionWithName:@"ui_rect_vs"];
|
|
id<MTLFunction> ui_rect_fragment_func =
|
|
[library newFunctionWithName:@"ui_rect_fs"];
|
|
|
|
MTLRenderPipelineDescriptor *pipeline_descriptor =
|
|
[[MTLRenderPipelineDescriptor alloc] init];
|
|
[pipeline_descriptor setVertexFunction:vertex_func];
|
|
[pipeline_descriptor setFragmentFunction:fragment_func];
|
|
pipeline_descriptor.colorAttachments[0].pixelFormat =
|
|
MTLPixelFormatRGBA8Unorm;
|
|
pipeline_descriptor.colorAttachments[0].alphaBlendOperation =
|
|
MTLBlendOperationAdd;
|
|
pipeline_descriptor.colorAttachments[0].sourceAlphaBlendFactor =
|
|
MTLBlendFactorSourceAlpha;
|
|
pipeline_descriptor.colorAttachments[0].destinationAlphaBlendFactor =
|
|
MTLBlendFactorOneMinusSourceAlpha;
|
|
pipeline_descriptor.colorAttachments[0].sourceRGBBlendFactor =
|
|
MTLBlendFactorSourceAlpha;
|
|
pipeline_descriptor.colorAttachments[0].destinationRGBBlendFactor =
|
|
MTLBlendFactorOneMinusSourceAlpha;
|
|
pipeline_descriptor.colorAttachments[0].blendingEnabled = true;
|
|
|
|
MTLRenderPipelineDescriptor *ui_rect_pipeline_descriptor =
|
|
[[MTLRenderPipelineDescriptor alloc] init];
|
|
[ui_rect_pipeline_descriptor setVertexFunction:ui_rect_vertex_func];
|
|
[ui_rect_pipeline_descriptor setFragmentFunction:ui_rect_fragment_func];
|
|
ui_rect_pipeline_descriptor.colorAttachments[0].pixelFormat =
|
|
MTLPixelFormatRGBA8Unorm;
|
|
ui_rect_pipeline_descriptor.colorAttachments[0].alphaBlendOperation =
|
|
MTLBlendOperationAdd;
|
|
ui_rect_pipeline_descriptor.colorAttachments[0].sourceAlphaBlendFactor =
|
|
MTLBlendFactorSourceAlpha;
|
|
ui_rect_pipeline_descriptor.colorAttachments[0].destinationAlphaBlendFactor =
|
|
MTLBlendFactorOneMinusSourceAlpha;
|
|
ui_rect_pipeline_descriptor.colorAttachments[0].sourceRGBBlendFactor =
|
|
MTLBlendFactorSourceAlpha;
|
|
ui_rect_pipeline_descriptor.colorAttachments[0].destinationRGBBlendFactor =
|
|
MTLBlendFactorOneMinusSourceAlpha;
|
|
ui_rect_pipeline_descriptor.colorAttachments[0].blendingEnabled = true;
|
|
|
|
NSError *pipeline_error = NULL;
|
|
array(_MTLRenderPipelineState) pipelines =
|
|
newArray(_MTLRenderPipelineState, 2);
|
|
pushArray(_MTLRenderPipelineState, &pipelines,
|
|
[device newRenderPipelineStateWithDescriptor:pipeline_descriptor
|
|
error:&pipeline_error]);
|
|
if (pipeline_error != NULL) {
|
|
if (pipeline_error.description != NULL) {
|
|
NSLog(@"Error description: %@\n", pipeline_error.description);
|
|
}
|
|
|
|
exit(1);
|
|
}
|
|
pushArray(_MTLRenderPipelineState, &pipelines,
|
|
[device
|
|
newRenderPipelineStateWithDescriptor:ui_rect_pipeline_descriptor
|
|
error:&pipeline_error]);
|
|
|
|
array(_MTLBuffer) buffers = newArray(_MTLBuffer, 8);
|
|
array(_MTLTexture) textures = newArray(_MTLTexture, 8);
|
|
|
|
if (pipeline_error != NULL) {
|
|
if (pipeline_error.description != NULL) {
|
|
NSLog(@"Error description: %@\n", pipeline_error.description);
|
|
}
|
|
|
|
exit(1);
|
|
}
|
|
|
|
return (_metal_gfx_context){
|
|
.application = application,
|
|
.window = window,
|
|
.view = view,
|
|
.keep_running = true,
|
|
|
|
.device = device,
|
|
.metal_layer = metal_layer,
|
|
.library = library,
|
|
.command_queue = command_queue,
|
|
.pipelines = pipelines,
|
|
.buffers = buffers,
|
|
.textures = textures,
|
|
};
|
|
}
|
|
|
|
static void _metal_gfx_send_events(_metal_gfx_context *cx) {
|
|
NSEvent *event = [cx->application nextEventMatchingMask:NSEventMaskAny
|
|
untilDate:[NSDate distantPast]
|
|
inMode:NSDefaultRunLoopMode
|
|
dequeue:YES];
|
|
|
|
[cx->application sendEvent:event];
|
|
}
|
|
|
|
static void _metal_gfx_present(_metal_gfx_context *cx) {
|
|
_gfx_context.gpu_glyphs.size = 0;
|
|
_gfx_context.gpu_ui_rects.size = 0;
|
|
_gfx_context.frame_func(cx->mouse_x, _gfx_context.frame_height - cx->mouse_y,
|
|
cx->mouse_left_down, cx->mouse_right_down);
|
|
|
|
if (_gfx_context.gpu_glyphs.size > 0) {
|
|
gfx_update_buffer(&_gfx_context, 2, _gfx_context.gpu_glyphs.data,
|
|
_gfx_context.gpu_glyphs.size * sizeof(GpuGlyph));
|
|
}
|
|
if (_gfx_context.gpu_ui_rects.size > 0) {
|
|
gfx_update_buffer(&_gfx_context, 4, _gfx_context.gpu_ui_rects.data,
|
|
_gfx_context.gpu_ui_rects.size * sizeof(GpuUiRect));
|
|
}
|
|
|
|
GpuUniformParams gpu_uniform_params = {
|
|
.screen_size =
|
|
{
|
|
(float)_gfx_context.frame_width,
|
|
(float)_gfx_context.frame_height,
|
|
},
|
|
.font_size =
|
|
{
|
|
(float)_FONT_WIDTH,
|
|
(float)_FONT_HEIGHT,
|
|
},
|
|
};
|
|
|
|
gfx_update_buffer(&_gfx_context, 3, &gpu_uniform_params,
|
|
sizeof(GpuUniformParams));
|
|
|
|
@autoreleasepool {
|
|
id<CAMetalDrawable> drawable = [cx->metal_layer nextDrawable];
|
|
|
|
id<MTLCommandBuffer> command_buffer = [cx->command_queue commandBuffer];
|
|
MTLRenderPassDescriptor *render_pass_desc =
|
|
[MTLRenderPassDescriptor renderPassDescriptor];
|
|
render_pass_desc.colorAttachments[0].texture = drawable.texture;
|
|
render_pass_desc.colorAttachments[0].loadAction = MTLLoadActionClear;
|
|
render_pass_desc.colorAttachments[0].clearColor =
|
|
MTLClearColorMake(0.1, 0.1, 0.1, 1);
|
|
render_pass_desc.colorAttachments[0].storeAction = MTLStoreActionStore;
|
|
|
|
id<MTLRenderCommandEncoder> encoder =
|
|
[command_buffer renderCommandEncoderWithDescriptor:render_pass_desc];
|
|
|
|
if (_gfx_context.gpu_ui_rects.size > 0) {
|
|
// UI Rects
|
|
[encoder setRenderPipelineState:cx->pipelines.data[1]];
|
|
// FIXME: allow these to be described by the user instead of
|
|
// hardcoded
|
|
[encoder setVertexBuffer:cx->buffers.data[0]
|
|
offset:0
|
|
atIndex:0]; // vertices
|
|
[encoder setVertexBuffer:cx->buffers.data[4]
|
|
offset:0
|
|
atIndex:1]; // ui rects
|
|
[encoder setVertexBuffer:cx->buffers.data[3]
|
|
offset:0
|
|
atIndex:2]; // uniforms
|
|
[encoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
|
|
indexCount:6
|
|
indexType:MTLIndexTypeUInt16
|
|
indexBuffer:cx->buffers.data[1]
|
|
indexBufferOffset:0
|
|
instanceCount:_gfx_context.gpu_ui_rects.size];
|
|
}
|
|
|
|
if (_gfx_context.gpu_glyphs.size > 0) {
|
|
// UI Text
|
|
[encoder setRenderPipelineState:cx->pipelines.data[0]];
|
|
// FIXME: allow these to be described by the user instead of
|
|
// hardcoded
|
|
[encoder setVertexBuffer:cx->buffers.data[0]
|
|
offset:0
|
|
atIndex:0]; // vertices
|
|
[encoder setVertexBuffer:cx->buffers.data[2]
|
|
offset:0
|
|
atIndex:1]; // glyph data
|
|
[encoder setVertexBuffer:cx->buffers.data[3]
|
|
offset:0
|
|
atIndex:2]; // uniforms
|
|
[encoder setFragmentTexture:cx->textures.data[0] atIndex:0];
|
|
[encoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
|
|
indexCount:6
|
|
indexType:MTLIndexTypeUInt16
|
|
indexBuffer:cx->buffers.data[1]
|
|
indexBufferOffset:0
|
|
instanceCount:_gfx_context.gpu_glyphs.size];
|
|
}
|
|
|
|
[encoder endEncoding];
|
|
// FIXME: `afterMinimumDuration` causes the weird re-size scaling, but I
|
|
// need to figure why the heck the NSView doesn't get the rendered
|
|
// contents unless `afterMinimumDuration` is here
|
|
if (cx->refresh_now) {
|
|
[command_buffer presentDrawable:drawable];
|
|
cx->refresh_now = false;
|
|
} else {
|
|
[command_buffer presentDrawable:drawable
|
|
afterMinimumDuration:1.0 / 144.0];
|
|
}
|
|
[command_buffer commit];
|
|
|
|
[command_buffer waitUntilScheduled];
|
|
}
|
|
}
|
|
|
|
static size_t _metal_gfx_push_texture_buffer(_metal_gfx_context *cx,
|
|
uint32_t width, uint32_t height,
|
|
const void *data, size_t len) {
|
|
MTLTextureDescriptor *texture_desc = [MTLTextureDescriptor
|
|
texture2DDescriptorWithPixelFormat:MTLPixelFormatR8Unorm
|
|
width:width
|
|
height:height
|
|
mipmapped:false];
|
|
_MTLTexture texture = [cx->device newTextureWithDescriptor:texture_desc];
|
|
|
|
MTLRegion region = MTLRegionMake2D(0, 0, width, height);
|
|
[texture replaceRegion:region
|
|
mipmapLevel:0
|
|
slice:0
|
|
withBytes:data
|
|
bytesPerRow:width * sizeof(uint8_t)
|
|
bytesPerImage:len];
|
|
|
|
pushArray(_MTLTexture, &cx->textures, texture);
|
|
return cx->textures.size - 1;
|
|
}
|
|
|
|
static void _metal_gfx_resize_texture_buffer(_metal_gfx_context *cx,
|
|
uint32_t width,
|
|
size_t texture_index,
|
|
uint32_t height) {
|
|
[cx->textures.data[texture_index] setPurgeableState:MTLPurgeableStateEmpty];
|
|
[cx->textures.data[texture_index] release];
|
|
|
|
MTLTextureDescriptor *texture_desc = [MTLTextureDescriptor
|
|
texture2DDescriptorWithPixelFormat:MTLPixelFormatR8Unorm
|
|
width:width
|
|
height:height
|
|
mipmapped:false];
|
|
cx->textures.data[texture_index] =
|
|
[cx->device newTextureWithDescriptor:texture_desc];
|
|
}
|
|
|
|
size_t _metal_gfx_push_vertex_buffer(_metal_gfx_context *cx, const void *data,
|
|
size_t len) {
|
|
pushArray(_MTLBuffer, &cx->buffers,
|
|
[cx->device newBufferWithBytes:data
|
|
length:len
|
|
options:MTLResourceStorageModeShared]);
|
|
|
|
return cx->buffers.size - 1;
|
|
}
|
|
static size_t _metal_gfx_allocate_vertex_buffer(_metal_gfx_context *cx,
|
|
size_t len) {
|
|
pushArray(_MTLBuffer, &cx->buffers,
|
|
[cx->device newBufferWithLength:len
|
|
options:MTLResourceStorageModeShared]);
|
|
|
|
return cx->buffers.size - 1;
|
|
}
|
|
static void _metal_gfx_update_buffer(_metal_gfx_context *cx,
|
|
size_t buffer_index, const void *data,
|
|
size_t len) {
|
|
void *buffer_contents = [cx->buffers.data[buffer_index] contents];
|
|
|
|
// FIXME: actually check to see if this will fit in the buffer
|
|
memcpy(buffer_contents, data, len);
|
|
}
|
|
#elif __linux__
|
|
#include <linux/input-event-codes.h>
|
|
|
|
static void _wayland_pointer_enter(void *data, struct wl_pointer *pointer,
|
|
uint32_t serial, struct wl_surface *surface,
|
|
wl_fixed_t x, wl_fixed_t y) {
|
|
// fprintf(stderr, "pointer enter: %d, %d\n", x, y);
|
|
}
|
|
static void _wayland_pointer_leave(void *data, struct wl_pointer *pointer,
|
|
uint32_t serial,
|
|
struct wl_surface *surface) {
|
|
// fprintf(stderr, "pointer leave\n");
|
|
}
|
|
static void _wayland_pointer_button(void *data, struct wl_pointer *pointer,
|
|
uint32_t serial, uint32_t time,
|
|
uint32_t button, uint32_t state) {
|
|
_opengl_gfx_context_wayland *cx = data;
|
|
|
|
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
|
if (button == BTN_LEFT) {
|
|
cx->mouse_left_down = true;
|
|
} else if (button == BTN_RIGHT) {
|
|
cx->mouse_right_down = true;
|
|
}
|
|
} else if (state == WL_POINTER_BUTTON_STATE_RELEASED) {
|
|
if (button == BTN_LEFT) {
|
|
cx->mouse_left_down = false;
|
|
} else if (button == BTN_RIGHT) {
|
|
cx->mouse_right_down = false;
|
|
}
|
|
}
|
|
}
|
|
static void _wayland_pointer_axis(void *data, struct wl_pointer *pointer,
|
|
uint32_t time, uint32_t axis,
|
|
wl_fixed_t value) {}
|
|
static void _wayland_pointer_motion(void *data, struct wl_pointer *pointer,
|
|
uint32_t time, wl_fixed_t x, wl_fixed_t y) {
|
|
_opengl_gfx_context_wayland *cx = data;
|
|
cx->mouse_x = wl_fixed_to_int(x);
|
|
cx->mouse_y = wl_fixed_to_int(y);
|
|
}
|
|
|
|
static const struct wl_pointer_listener pointer_listener = {
|
|
.enter = _wayland_pointer_enter,
|
|
.leave = _wayland_pointer_leave,
|
|
.motion = _wayland_pointer_motion,
|
|
.button = _wayland_pointer_button,
|
|
.axis = _wayland_pointer_axis,
|
|
};
|
|
|
|
static void _wayland_xdg_toplevel_configure(void *data,
|
|
struct xdg_toplevel *xdg_toplevel,
|
|
int32_t width, int32_t height,
|
|
struct wl_array *states) {}
|
|
|
|
static void _wayland_xdg_toplevel_close(void *data,
|
|
struct xdg_toplevel *xdg_toplevel) {
|
|
keep_running = false;
|
|
}
|
|
|
|
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
|
|
_wayland_xdg_toplevel_configure,
|
|
_wayland_xdg_toplevel_close,
|
|
};
|
|
|
|
static void _wayland_xdg_surface_configure(void *data,
|
|
struct xdg_surface *xdg_surface,
|
|
uint32_t serial) {
|
|
xdg_surface_ack_configure(xdg_surface, serial);
|
|
}
|
|
|
|
static const struct xdg_surface_listener xdg_surface_listener = {
|
|
_wayland_xdg_surface_configure,
|
|
};
|
|
|
|
static void _wayland_xdg_wm_base_ping(void *data, struct xdg_wm_base *shell,
|
|
uint32_t serial) {
|
|
xdg_wm_base_pong(shell, serial);
|
|
}
|
|
static const struct xdg_wm_base_listener xdg_wm_base_listener = {
|
|
_wayland_xdg_wm_base_ping,
|
|
};
|
|
|
|
static void _wayland_registry_handle_global(void *data,
|
|
struct wl_registry *registry,
|
|
uint32_t name,
|
|
const char *interface,
|
|
uint32_t version) {
|
|
_opengl_gfx_context_wayland *d = data;
|
|
|
|
fprintf(stderr, "global: %s\n", interface);
|
|
|
|
if (strcmp(interface, "wl_compositor") == 0) {
|
|
d->compositor =
|
|
wl_registry_bind(registry, name, &wl_compositor_interface, 3);
|
|
} else if (strcmp(interface, "wl_shm") == 0) {
|
|
d->shared_memory = wl_registry_bind(registry, name, &wl_shm_interface, 1);
|
|
} else if (strcmp(interface, "xdg_wm_base") == 0) {
|
|
d->wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
|
|
} else if (strcmp(interface, "wl_seat") == 0) {
|
|
d->seat = wl_registry_bind(registry, name, &wl_seat_interface, 1);
|
|
}
|
|
}
|
|
|
|
static void _wayland_registry_handle_global_remove(void *data,
|
|
struct wl_registry *registry,
|
|
uint32_t name) {}
|
|
|
|
static const struct wl_registry_listener registry_listener = {
|
|
_wayland_registry_handle_global,
|
|
_wayland_registry_handle_global_remove,
|
|
};
|
|
|
|
static void _opengl_gfx_message_callback(GLenum source, GLenum type, GLenum id,
|
|
GLenum severity, GLsizei length,
|
|
const GLchar *message,
|
|
const void *user_param) {
|
|
fprintf(stderr,
|
|
"GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
|
|
type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "", type, severity,
|
|
message);
|
|
}
|
|
|
|
static void _opengl_gfx_check_shader_error(string msg, GLuint shader,
|
|
GLuint status) {
|
|
GLint good = 0;
|
|
glGetShaderiv(shader, status, &good);
|
|
if (good == GL_FALSE) {
|
|
GLint max_length = 0;
|
|
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &max_length);
|
|
|
|
uint8_t *log_buffer = malloc(max_length + 1);
|
|
glGetShaderInfoLog(shader, max_length, &max_length, log_buffer);
|
|
glDeleteShader(shader);
|
|
|
|
fprintf(stderr, "%.*s: %.*s\n", msg.len, msg.data, max_length, log_buffer);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void _opengl_gfx_check_shader_program_error(string msg,
|
|
GLuint shader_program,
|
|
GLuint status) {
|
|
GLint good = 0;
|
|
glGetProgramiv(shader_program, status, &good);
|
|
if (good == GL_FALSE) {
|
|
GLint max_length = 0;
|
|
glGetProgramiv(shader_program, GL_INFO_LOG_LENGTH, &max_length);
|
|
|
|
uint8_t *log_buffer = malloc(max_length + 1);
|
|
glGetProgramInfoLog(shader_program, max_length, &max_length, log_buffer);
|
|
glDeleteProgram(shader_program);
|
|
|
|
fprintf(stderr, "%.*s: %.*s\n", msg.len, msg.data, max_length, log_buffer);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static GLuint _opengl_gfx_compile_shader(string file_path, GLuint shader_type) {
|
|
GLuint shader = glCreateShader(shader_type);
|
|
size_t shader_file_size = get_file_size(file_path);
|
|
uint8_t *shader_file_data = malloc(shader_file_size + 1);
|
|
load_file(file_path, shader_file_size, shader_file_data);
|
|
shader_file_data[shader_file_size] = 0;
|
|
// fprintf(stderr, "%s\n", shader_file_data);
|
|
|
|
glShaderSource(shader, 1, &shader_file_data, NULL);
|
|
glCompileShader(shader);
|
|
|
|
_opengl_gfx_check_shader_error(_String("failed to compile shader"), shader,
|
|
GL_COMPILE_STATUS);
|
|
|
|
return shader;
|
|
}
|
|
|
|
static _opengl_gfx_context_wayland
|
|
_opengl_gfx_init_context_wayland(uint32_t width, uint32_t height) {
|
|
_opengl_gfx_context_wayland cx = {0};
|
|
|
|
cx.display = wl_display_connect(NULL);
|
|
if (!cx.display) {
|
|
fprintf(stderr, "Failed to connect to Wayland display\n");
|
|
exit(1);
|
|
}
|
|
struct wl_registry *registry = wl_display_get_registry(cx.display);
|
|
wl_registry_add_listener(registry, ®istry_listener, &cx);
|
|
|
|
// wait for all the globals to be registered
|
|
wl_display_roundtrip(cx.display);
|
|
xdg_wm_base_add_listener(cx.wm_base, &xdg_wm_base_listener,
|
|
&_gfx_context.backend);
|
|
|
|
cx.pointer = wl_seat_get_pointer(cx.seat);
|
|
wl_pointer_add_listener(cx.pointer, &pointer_listener, &_gfx_context.backend);
|
|
|
|
cx.surface = wl_compositor_create_surface(cx.compositor);
|
|
cx.xdg_surface = xdg_wm_base_get_xdg_surface(cx.wm_base, cx.surface);
|
|
xdg_surface_add_listener(cx.xdg_surface, &xdg_surface_listener,
|
|
&_gfx_context.backend);
|
|
|
|
cx.xdg_toplevel = xdg_surface_get_toplevel(cx.xdg_surface);
|
|
xdg_toplevel_add_listener(cx.xdg_toplevel, &xdg_toplevel_listener,
|
|
&_gfx_context.backend);
|
|
xdg_toplevel_set_title(cx.xdg_toplevel, "chat - [Slack sux]");
|
|
xdg_toplevel_set_app_id(cx.xdg_toplevel, "nl.spacegirl.a_chat_client");
|
|
|
|
wl_surface_commit(cx.surface);
|
|
wl_display_roundtrip(cx.display);
|
|
|
|
int buffer_size = width * height * 4;
|
|
int fd = syscall(SYS_memfd_create, "buffer", 0);
|
|
ftruncate(fd, buffer_size);
|
|
|
|
cx.pixels =
|
|
mmap(NULL, buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
for (int i = 0; i < buffer_size; i++) {
|
|
cx.pixels[i] = 0;
|
|
}
|
|
|
|
cx.shared_memory_pool = wl_shm_create_pool(cx.shared_memory, fd, buffer_size);
|
|
cx.buffer = wl_shm_pool_create_buffer(cx.shared_memory_pool, 0, width, height,
|
|
width * 4, WL_SHM_FORMAT_ARGB8888);
|
|
|
|
wl_surface_attach(cx.surface, cx.buffer, 0, 0);
|
|
wl_surface_commit(cx.surface);
|
|
|
|
/* Init EGL */
|
|
EGLint major, minor, count, n, size;
|
|
EGLConfig *configs;
|
|
EGLint config_attribs[] = {
|
|
EGL_SURFACE_TYPE,
|
|
EGL_WINDOW_BIT,
|
|
//
|
|
EGL_RED_SIZE,
|
|
8,
|
|
//
|
|
EGL_BLUE_SIZE,
|
|
8,
|
|
//
|
|
EGL_GREEN_SIZE,
|
|
8,
|
|
//
|
|
EGL_RENDERABLE_TYPE,
|
|
EGL_OPENGL_BIT,
|
|
//
|
|
EGL_NONE,
|
|
};
|
|
|
|
static const EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2,
|
|
EGL_NONE};
|
|
|
|
cx.egl_display = eglGetDisplay(cx.display);
|
|
if (cx.egl_display == EGL_NO_DISPLAY) {
|
|
fprintf(stderr, "Failed to create EGL display\n");
|
|
exit(1);
|
|
}
|
|
fprintf(stderr, "Created EGL display\n");
|
|
|
|
if (eglInitialize(cx.egl_display, &major, &minor) != EGL_TRUE) {
|
|
fprintf(stderr, "Failed to initialize EGL display\n");
|
|
exit(1);
|
|
}
|
|
fprintf(stderr, "EGL major: %d, minor: %d\n", major, minor);
|
|
|
|
eglGetConfigs(cx.egl_display, NULL, 0, &count);
|
|
configs = calloc(count, sizeof(EGLConfig));
|
|
|
|
eglChooseConfig(cx.egl_display, config_attribs, configs, count, &n);
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
eglGetConfigAttrib(cx.egl_display, configs[i], EGL_BUFFER_SIZE, &size);
|
|
fprintf(stderr, "EGL Buffer size: %d\n", size);
|
|
|
|
eglGetConfigAttrib(cx.egl_display, configs[i], EGL_RED_SIZE, &size);
|
|
fprintf(stderr, "EGL Red size: %d\n", size);
|
|
|
|
cx.egl_config = configs[i];
|
|
break;
|
|
}
|
|
|
|
eglBindAPI(EGL_OPENGL_API);
|
|
cx.egl_context = eglCreateContext(cx.egl_display, cx.egl_config,
|
|
EGL_NO_CONTEXT, context_attribs);
|
|
|
|
cx.egl_window = wl_egl_window_create(cx.surface, width, height);
|
|
if (cx.egl_window == EGL_NO_SURFACE) {
|
|
fprintf(stderr, "Failed to create EGL window\n");
|
|
exit(1);
|
|
}
|
|
fprintf(stderr, "Created EGL window\n");
|
|
|
|
cx.egl_surface = eglCreateWindowSurface(cx.egl_display, cx.egl_config,
|
|
cx.egl_window, NULL);
|
|
if (eglMakeCurrent(cx.egl_display, cx.egl_surface, cx.egl_surface,
|
|
cx.egl_context) != EGL_TRUE) {
|
|
fprintf(stderr, "eglMakeCurrent() failed\n");
|
|
}
|
|
|
|
glEnable(GL_DEBUG_OUTPUT);
|
|
glDebugMessageCallback(_opengl_gfx_message_callback, NULL);
|
|
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glBlendFunc(GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
cx.ui_rect_vertex_shader = _opengl_gfx_compile_shader(
|
|
_String("shaders/ui_rect_vertex.glsl"), GL_VERTEX_SHADER);
|
|
cx.ui_rect_fragment_shader = _opengl_gfx_compile_shader(
|
|
_String("shaders/ui_rect_fragment.glsl"), GL_FRAGMENT_SHADER);
|
|
cx.ui_rect_shader_program = glCreateProgram();
|
|
glAttachShader(cx.ui_rect_shader_program, cx.ui_rect_vertex_shader);
|
|
glAttachShader(cx.ui_rect_shader_program, cx.ui_rect_fragment_shader);
|
|
glLinkProgram(cx.ui_rect_shader_program);
|
|
_opengl_gfx_check_shader_program_error(
|
|
_String("failed to link ui_rect shader program"),
|
|
cx.ui_rect_shader_program, GL_LINK_STATUS);
|
|
|
|
cx.text_atlas_vertex_shader = _opengl_gfx_compile_shader(
|
|
_String("shaders/text_atlas_vertex.glsl"), GL_VERTEX_SHADER);
|
|
cx.text_atlas_fragment_shader = _opengl_gfx_compile_shader(
|
|
_String("shaders/text_atlas_fragment.glsl"), GL_FRAGMENT_SHADER);
|
|
cx.text_atlas_shader_program = glCreateProgram();
|
|
glAttachShader(cx.text_atlas_shader_program, cx.text_atlas_vertex_shader);
|
|
glAttachShader(cx.text_atlas_shader_program, cx.text_atlas_fragment_shader);
|
|
glLinkProgram(cx.text_atlas_shader_program);
|
|
_opengl_gfx_check_shader_program_error(
|
|
_String("failed to link text_atlas shader program"),
|
|
cx.text_atlas_shader_program, GL_LINK_STATUS);
|
|
|
|
cx.buffers = newArray(GLuint, 8);
|
|
cx.textures = newArray(GLuint, 8);
|
|
/* ******** */
|
|
|
|
return cx;
|
|
}
|
|
|
|
static _opengl_gfx_context_x11 _opengl_gfx_init_context_11(uint32_t width,
|
|
uint32_t height) {
|
|
Display *display = XOpenDisplay(NULL);
|
|
if (display == NULL) {
|
|
fprintf(stderr, "Failed to open X display\n");
|
|
exit(1);
|
|
}
|
|
|
|
int screen = DefaultScreen(display);
|
|
Window window = XCreateSimpleWindow(
|
|
display, RootWindow(display, screen), 0, 0, width, height, 1,
|
|
BlackPixel(display, screen), WhitePixel(display, screen));
|
|
XSelectInput(display, window,
|
|
ExposureMask | KeyPressMask | ButtonPressMask |
|
|
ButtonReleaseMask);
|
|
XMapWindow(display, window);
|
|
|
|
return (_opengl_gfx_context_x11){
|
|
.display = display,
|
|
.window = window,
|
|
.screen = screen,
|
|
};
|
|
}
|
|
|
|
static void _opengl_gfx_send_events_wayland(_opengl_gfx_context_wayland *cx) {
|
|
wl_display_dispatch(cx->display);
|
|
|
|
// TODO: don't just render like crazy, limit framerate
|
|
_opengl_gfx_present_wayland(cx);
|
|
}
|
|
|
|
static void _opengl_gfx_send_events_x11(_opengl_gfx_context_x11 *cx) {
|
|
XEvent e;
|
|
XNextEvent(cx->display, &e);
|
|
if (e.type == Expose) {
|
|
XFillRectangle(cx->display, cx->window, DefaultGC(cx->display, cx->screen),
|
|
20, 20, 10, 10);
|
|
}
|
|
|
|
if (e.type == KeyPress) {
|
|
keep_running = false;
|
|
|
|
XCloseDisplay(cx->display);
|
|
}
|
|
}
|
|
|
|
static void _opengl_gfx_present_wayland(_opengl_gfx_context_wayland *cx) {
|
|
_gfx_context.gpu_glyphs.size = 0;
|
|
_gfx_context.gpu_ui_rects.size = 0;
|
|
_gfx_context.frame_func(cx->mouse_x, cx->mouse_y, cx->mouse_left_down,
|
|
cx->mouse_right_down);
|
|
|
|
if (_gfx_context.gpu_glyphs.size > 0) {
|
|
gfx_update_buffer(&_gfx_context, 2, _gfx_context.gpu_glyphs.data,
|
|
_gfx_context.gpu_glyphs.size * sizeof(GpuGlyph));
|
|
}
|
|
if (_gfx_context.gpu_ui_rects.size > 0) {
|
|
gfx_update_buffer(&_gfx_context, 4, _gfx_context.gpu_ui_rects.data,
|
|
_gfx_context.gpu_ui_rects.size * sizeof(GpuUiRect));
|
|
}
|
|
|
|
GpuUniformParams gpu_uniform_params = {
|
|
.screen_size =
|
|
{
|
|
(float)_gfx_context.frame_width,
|
|
(float)_gfx_context.frame_height,
|
|
},
|
|
.font_size =
|
|
{
|
|
(float)_FONT_WIDTH,
|
|
(float)_FONT_HEIGHT,
|
|
},
|
|
};
|
|
|
|
gfx_update_buffer(&_gfx_context, 3, &gpu_uniform_params,
|
|
sizeof(GpuUniformParams));
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
glViewport(0, 0, _gfx_context.frame_width, _gfx_context.frame_height);
|
|
|
|
if (_gfx_context.gpu_ui_rects.size > 0) {
|
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, cx->buffers.data[0]);
|
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, cx->buffers.data[4]);
|
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, cx->buffers.data[3]);
|
|
glUseProgram(cx->ui_rect_shader_program);
|
|
|
|
const uint16_t indices[] = {0, 1, 2, 0, 2, 3};
|
|
glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices,
|
|
_gfx_context.gpu_ui_rects.size);
|
|
}
|
|
|
|
if (_gfx_context.gpu_glyphs.size > 0) {
|
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, cx->buffers.data[0]);
|
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, cx->buffers.data[2]);
|
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, cx->buffers.data[3]);
|
|
glUseProgram(cx->text_atlas_shader_program);
|
|
|
|
const uint16_t indices[] = {0, 1, 2, 0, 2, 3};
|
|
glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices,
|
|
_gfx_context.gpu_glyphs.size);
|
|
}
|
|
|
|
glFlush();
|
|
|
|
if (eglSwapBuffers(cx->egl_display, cx->egl_surface) != EGL_TRUE) {
|
|
fprintf(stderr, "eglSwapBuffers() failed\n");
|
|
}
|
|
}
|
|
|
|
static size_t
|
|
_opengl_gfx_push_texture_buffer_wayland(_opengl_gfx_context_wayland *cx,
|
|
uint32_t width, uint32_t height,
|
|
const void *data, size_t len) {
|
|
pushArray(GLuint, &cx->textures, 0);
|
|
glCreateTextures(GL_TEXTURE_2D, 1, &cx->textures.data[cx->textures.size - 1]);
|
|
glTextureParameteri(cx->textures.data[cx->textures.size - 1],
|
|
GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTextureParameteri(cx->textures.data[cx->textures.size - 1],
|
|
GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTextureStorage2D(cx->textures.data[cx->textures.size - 1], 1, GL_RGBA8,
|
|
width, height);
|
|
glTextureSubImage2D(cx->textures.data[cx->textures.size - 1], 0, 0, 0, width,
|
|
height, GL_RED, GL_UNSIGNED_BYTE, data);
|
|
glBindTextureUnit(cx->textures.size - 1,
|
|
cx->textures.data[cx->textures.size - 1]);
|
|
|
|
return cx->textures.size - 1;
|
|
}
|
|
|
|
static void
|
|
_opengl_gfx_resize_texture_buffer_wayland(_opengl_gfx_context_wayland *cx,
|
|
uint32_t width, size_t texture_index,
|
|
uint32_t height) {
|
|
// TODO
|
|
assert(false && "_opengl_gfx_resize_texture_buffer_wayland unimplemented");
|
|
}
|
|
|
|
size_t _opengl_gfx_push_vertex_buffer_wayland(_opengl_gfx_context_wayland *cx,
|
|
const void *data, size_t len) {
|
|
pushArray(GLuint, &cx->buffers, 0);
|
|
glCreateBuffers(1, &cx->buffers.data[cx->buffers.size - 1]);
|
|
glNamedBufferStorage(cx->buffers.data[cx->buffers.size - 1], len, data,
|
|
GL_DYNAMIC_STORAGE_BIT);
|
|
|
|
return cx->buffers.size - 1;
|
|
}
|
|
static size_t
|
|
_opengl_gfx_allocate_vertex_buffer_wayland(_opengl_gfx_context_wayland *cx,
|
|
size_t len) {
|
|
pushArray(GLuint, &cx->buffers, 0);
|
|
glCreateBuffers(1, &cx->buffers.data[cx->buffers.size - 1]);
|
|
glNamedBufferStorage(cx->buffers.data[cx->buffers.size - 1], len, (void *)0,
|
|
GL_DYNAMIC_STORAGE_BIT);
|
|
|
|
return cx->buffers.size - 1;
|
|
}
|
|
static void _opengl_gfx_update_buffer_wayland(_opengl_gfx_context_wayland *cx,
|
|
size_t buffer_index,
|
|
const void *data, size_t len) {
|
|
glNamedBufferSubData(cx->buffers.data[buffer_index], 0, len, data);
|
|
}
|
|
#endif
|
|
|
|
void gfx_run_events(gfx_context_t *cx) {
|
|
#if defined(__APPLE__)
|
|
return _metal_gfx_send_events(&cx->backend);
|
|
#elif __linux__
|
|
return _opengl_gfx_send_events_wayland(&cx->backend);
|
|
#else
|
|
#error "Unsupported graphics backend"
|
|
#endif
|
|
}
|
|
|
|
size_t gfx_push_texture_buffer(gfx_context_t *cx, uint32_t width,
|
|
uint32_t height, const void *data, size_t len) {
|
|
#if defined(__APPLE__)
|
|
return _metal_gfx_push_texture_buffer(&cx->backend, width, height, data, len);
|
|
#elif __linux__
|
|
return _opengl_gfx_push_texture_buffer_wayland(&cx->backend, width, height,
|
|
data, len);
|
|
#else
|
|
#error "Unsupported graphics backend"
|
|
#endif
|
|
}
|
|
|
|
size_t gfx_push_vertex_buffer(gfx_context_t *cx, const void *data, size_t len) {
|
|
#if defined(__APPLE__)
|
|
return _metal_gfx_push_vertex_buffer(&cx->backend, data, len);
|
|
#elif __linux__
|
|
return _opengl_gfx_push_vertex_buffer_wayland(&cx->backend, data, len);
|
|
#else
|
|
#error "Unsupported graphics backend"
|
|
#endif
|
|
}
|
|
|
|
size_t gfx_allocate_vertex_buffer(gfx_context_t *cx, size_t len) {
|
|
#if defined(__APPLE__)
|
|
return _metal_gfx_allocate_vertex_buffer(&cx->backend, len);
|
|
#elif __linux__
|
|
return _opengl_gfx_allocate_vertex_buffer_wayland(&cx->backend, len);
|
|
#else
|
|
#error "Unsupported graphics backend"
|
|
#endif
|
|
}
|
|
|
|
// FIXME: abstract different backends
|
|
void gfx_update_buffer(gfx_context_t *cx, size_t buffer_index, const void *data,
|
|
size_t len) {
|
|
#if defined(__APPLE__)
|
|
return _metal_gfx_update_buffer(&cx->backend, buffer_index, data, len);
|
|
#elif __linux__
|
|
return _opengl_gfx_update_buffer_wayland(&cx->backend, buffer_index, data,
|
|
len);
|
|
#else
|
|
#error "Unsupported graphics backend"
|
|
#endif
|
|
}
|
|
|
|
void gfx_queue_char(gfx_context_t *cx, uint8_t character, float position[2]) {
|
|
GpuGlyph glyph;
|
|
if (character >= 32) {
|
|
glyph = cx->glyph_cache.data[character - 32];
|
|
} else {
|
|
glyph = cx->glyph_cache.data[cx->glyph_cache.size - 1];
|
|
}
|
|
|
|
glyph.position[0] = position[0];
|
|
glyph.position[1] = position[1];
|
|
|
|
pushArray(GpuGlyph, &cx->gpu_glyphs, glyph);
|
|
}
|
|
|
|
void gfx_queue_text(gfx_context_t *cx, string text, float position[2],
|
|
float max_x, float max_y, float color[4]) {
|
|
float x = 0;
|
|
float y = 0;
|
|
for (size_t i = 0; i < text.len; ++i) {
|
|
GpuGlyph glyph;
|
|
if (text.data[i] >= 32) {
|
|
glyph = cx->glyph_cache.data[text.data[i] - 32];
|
|
} else {
|
|
glyph = cx->glyph_cache.data[cx->glyph_cache.size - 1];
|
|
}
|
|
|
|
size_t j;
|
|
float j_x = 0;
|
|
for (j = i; j < text.len; ++j) {
|
|
GpuGlyph glyph;
|
|
if (text.data[j] >= 32) {
|
|
glyph = cx->glyph_cache.data[text.data[j] - 32];
|
|
} else {
|
|
glyph = cx->glyph_cache.data[cx->glyph_cache.size - 1];
|
|
}
|
|
|
|
if (text.data[j] == 32) {
|
|
break;
|
|
}
|
|
|
|
j_x += glyph.size[0] / 2 + 4;
|
|
}
|
|
|
|
// if (x + j_x >= max_x) {
|
|
// x = 0;
|
|
// y += 24;
|
|
// }
|
|
// if (y >= max_y) {
|
|
// break;
|
|
// }
|
|
|
|
glyph.color[0] = color[0];
|
|
glyph.color[1] = color[1];
|
|
glyph.color[2] = color[2];
|
|
glyph.color[3] = color[3];
|
|
glyph.position[0] = x + position[0];
|
|
glyph.position[1] = y + position[1];
|
|
x += glyph.size[0] / 2 + 4;
|
|
|
|
pushArray(GpuGlyph, &cx->gpu_glyphs, glyph);
|
|
}
|
|
}
|
|
|
|
void gfx_queue_ui_rect(gfx_context_t *cx, float position[2], float size[2],
|
|
float border_size, float color[4]) {
|
|
GpuUiRect rect =
|
|
(GpuUiRect){.position = {position[0], position[1]},
|
|
.size = {size[0], size[1]},
|
|
.border_size = {border_size, border_size},
|
|
.color = {color[0], color[1], color[2], color[3]}};
|
|
|
|
pushArray(GpuUiRect, &cx->gpu_ui_rects, rect);
|
|
}
|
|
|
|
void gfx_add_glyph(gfx_context_t *cx, GpuGlyph glyph) {
|
|
pushArray(GpuGlyph, &cx->glyph_cache, glyph);
|
|
}
|
|
|
|
void *gfx_init_context(_gfx_frame_func frame_func, uint32_t width,
|
|
uint32_t height) {
|
|
__auto_type backend =
|
|
#if defined(__APPLE__)
|
|
_metal_gfx_init_context(width, height);
|
|
#elif __linux__
|
|
_opengl_gfx_init_context_wayland(width, height);
|
|
#else
|
|
#error "Unsupported graphics backend"
|
|
#endif
|
|
|
|
_gfx_context.backend = backend;
|
|
_gfx_context.frame_func = frame_func;
|
|
_gfx_context.frame_width = width;
|
|
_gfx_context.frame_height = height;
|
|
|
|
_gfx_context.gpu_ui_rects = newArray(GpuUiRect, 2000);
|
|
_gfx_context.gpu_glyphs = newArray(GpuGlyph, 8192 * 32);
|
|
_gfx_context.glyph_cache = newArray(GpuGlyph, 97);
|
|
|
|
const float vertices[] = {
|
|
// positions texture coords
|
|
-1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
|
|
1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 1.0f,
|
|
};
|
|
const uint16_t indices[] = {0, 1, 2, 0, 2, 3};
|
|
|
|
// NOTE: the order of these matter
|
|
gfx_push_vertex_buffer(&_gfx_context, vertices, sizeof(vertices));
|
|
gfx_push_vertex_buffer(&_gfx_context, indices, sizeof(indices));
|
|
gfx_allocate_vertex_buffer(&_gfx_context, _gfx_context.gpu_glyphs.capacity *
|
|
sizeof(GpuGlyph));
|
|
gfx_allocate_vertex_buffer(&_gfx_context, sizeof(GpuUniformParams));
|
|
gfx_allocate_vertex_buffer(&_gfx_context, _gfx_context.gpu_ui_rects.capacity *
|
|
sizeof(GpuUiRect));
|
|
|
|
return &_gfx_context;
|
|
}
|
|
|
|
#endif
|
|
#endif
|