diff --git a/shaders/text_atlas.metal b/shaders/text_atlas.metal index e5d99ea..2f95b7d 100644 --- a/shaders/text_atlas.metal +++ b/shaders/text_atlas.metal @@ -65,3 +65,69 @@ fragment float4 fs_main(VertexOutput in [[stage_in]], return float4(text_color * float3(1,1,1), text_color); } +struct UiRect { + float2 position; + float2 size; + float2 border_size; +}; + +struct UiRectFragment { + float4 device_position [[position]]; + float2 position; + float2 size; + float2 border_size; + float2 screen_size; + float2 tex_coord; +}; + +vertex UiRectFragment +ui_rect_vs( + uint vertex_id [[vertex_id]], + uint rect_id [[instance_id]], + constant VertexInput *vertices [[buffer(0)]], + constant UiRect *rects [[buffer(1)]], + constant UniformParams ¶ms [[buffer(2)]] +) +{ + UiRect rect = rects[rect_id]; + + return UiRectFragment { + float4(vertices[vertex_id].position, 1, 1), + rect.position, + rect.size, + rect.border_size, + params.screen_size, + vertices[vertex_id].tex_coord, + }; +} + +float rect_sdf( + float2 absolute_pixel_position, + float2 origin, + float2 size, + float corner_radius +) { + float2 half_size = size / 2; + float2 rect_center = origin + half_size; + + float2 pixel_position = abs(absolute_pixel_position - rect_center); + float2 shrunk_corner_position = half_size - corner_radius; + + float2 pixel_to_shrunk_corner = max(float2(0), pixel_position - shrunk_corner_position); + float distance_to_shrunk_corner = length(pixel_to_shrunk_corner); + float distance = distance_to_shrunk_corner - corner_radius; + + return distance; +} + +fragment float4 ui_rect_fs(UiRectFragment in [[stage_in]]) +{ + float2 pixel_pos = in.tex_coord.xy * in.screen_size; + + float distance = rect_sdf(pixel_pos, in.position, in.size, in.border_size.x); + if (distance <= 0.0) { + return float4(1,1,1,0.5); + } else { + return float4(0); + } +} diff --git a/src/gfx.h b/src/gfx.h index d2c216a..4b4e971 100644 --- a/src/gfx.h +++ b/src/gfx.h @@ -170,7 +170,6 @@ static _metal_gfx_context _metal_gfx_init_context(uint32_t width, uint32_t heigh [view.layer addSublayer:metal_layer]; view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay; - NSError *libraryError = NULL; NSURL *libraryURL = [[NSBundle mainBundle] URLForResource:@"./shaders" withExtension:@"metallib"]; if (libraryURL == NULL) { @@ -191,6 +190,8 @@ static _metal_gfx_context _metal_gfx_init_context(uint32_t width, uint32_t heigh id command_queue = [device newCommandQueue]; id vertex_func = [library newFunctionWithName:@"vs_main"]; id fragment_func = [library newFunctionWithName:@"fs_main"]; + id ui_rect_vertex_func = [library newFunctionWithName:@"ui_rect_vs"]; + id ui_rect_fragment_func = [library newFunctionWithName:@"ui_rect_fs"]; MTLRenderPipelineDescriptor *pipeline_descriptor = [[MTLRenderPipelineDescriptor alloc] init]; [pipeline_descriptor setVertexFunction:vertex_func]; @@ -203,9 +204,28 @@ static _metal_gfx_context _metal_gfx_init_context(uint32_t width, uint32_t heigh 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); @@ -255,16 +275,27 @@ void _metal_gfx_present(_metal_gfx_context *cx) { render_pass_desc.colorAttachments[0].storeAction = MTLStoreActionStore; id encoder = [command_buffer renderCommandEncoderWithDescriptor:render_pass_desc]; + + // 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]; - [encoder setVertexBuffer:cx->buffers.data[2] offset:0 atIndex:1]; - [encoder setVertexBuffer:cx->buffers.data[3] offset:0 atIndex:2]; + [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]; + // TODO: get instance count properly [encoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle indexCount:6 indexType:MTLIndexTypeUInt16 indexBuffer:cx->buffers.data[1] indexBufferOffset:0 instanceCount:36]; - [encoder endEncoding]; + // 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 + // TODO: get instance count properly + [encoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle indexCount:6 indexType:MTLIndexTypeUInt16 indexBuffer:cx->buffers.data[1] indexBufferOffset:0 instanceCount:3]; + + [encoder endEncoding]; [command_buffer presentDrawable:drawable]; [command_buffer commit]; diff --git a/src/main.c b/src/main.c index 8a6ef4f..ef549e9 100644 --- a/src/main.c +++ b/src/main.c @@ -73,6 +73,16 @@ void queue_text(string text, float position[2]) { } } +void queue_ui_rect(uint32_t position[2], uint32_t size[2], uint32_t border_size) { + GpuUiRect rect = (GpuUiRect) { + .position = { (float)position[0], (float)position[1] }, + .size = { (float)size[0], (float)size[1] }, + .border_size = { (float)border_size, (float)border_size }, + }; + + pushArray(GpuUiRect, &state.gpu_ui_rects, rect); +} + void ed_init(_gfx_frame_func frame_func) { state.gfx_cx = gfx_init_context(frame_func, 640, 480); state.ui_cx = ui_init_context(); @@ -96,6 +106,7 @@ void ed_init(_gfx_frame_func frame_func) { gfx_allocate_vertex_buffer(state.gfx_cx, state.gpu_glyphs.capacity * sizeof(GpuGlyph)); gfx_allocate_vertex_buffer(state.gfx_cx, sizeof(GpuUniformParams)); + gfx_allocate_vertex_buffer(state.gfx_cx, state.gpu_ui_rects.capacity * sizeof(GpuUiRect)); uint8_t ttf_buffer[1<<20]; // TODO: grab default font from the system @@ -178,11 +189,15 @@ void ed_frame() { ui_compute_layout(&state.ui_cx, 0); state.gpu_glyphs.size = 0; + state.gpu_ui_rects.size = 0; for (size_t i = 1; i < state.ui_cx.frame_elements.size; ++i) { string text = state.ui_cx.frame_elements.data[i].key; ui_element_frame_data *elm = &state.ui_cx.frame_elements.data[i]; queue_text(text, (float[]){ (float)elm->size.computed_pos[0], (float)elm->size.computed_pos[1] }); + + fprintf(stderr, "size[0]: %d, size[1]: %d\n", elm->size.computed_size[0], elm->size.computed_size[1]); + queue_ui_rect(elm->size.computed_pos, elm->size.computed_size, 16); } ui_update_cache(&state.ui_cx, 0); @@ -193,6 +208,10 @@ void ed_frame() { fprintf(stderr, "updated glyph buffer: %zu\n", state.gpu_glyphs.size); } + if (state.gpu_ui_rects.size > 0) { + gfx_update_buffer(state.gfx_cx, 4, state.gpu_ui_rects.data, state.gpu_ui_rects.size * sizeof(GpuUiRect)); + } + GpuUniformParams gpu_uniform_params = { .screen_size = { (float)state.gfx_cx->frame_width, diff --git a/src/ui.h b/src/ui.h index 630bcb9..2c937ce 100644 --- a/src/ui.h +++ b/src/ui.h @@ -155,7 +155,7 @@ size_t ui_element(ui_context *cx, string label) { .prev = cx->frame_elements.data[cx->current_parent].last, .parent = cx->current_parent, .size.semantic_size[0].type = UI_SEMANTIC_SIZE_FILL, - .size.semantic_size[1].type = UI_SEMANTIC_SIZE_FILL, + .size.semantic_size[1].type = UI_SEMANTIC_SIZE_FIT_TEXT, }; // Get cached element data @@ -210,7 +210,7 @@ static void _ui_compute_simple_layout(ui_context *cx, ui_element_frame_data *elm if (axis == UI_AXIS_HORIZONTAL) { elm->size.computed_size[axis] = elm->label.len * _FONT_WIDTH; } else if (axis == UI_AXIS_VERTICAL) { - elm->size.computed_size[axis] = _FONT_WIDTH; + elm->size.computed_size[axis] = _FONT_HEIGHT; } break; @@ -236,13 +236,13 @@ static void _ui_compute_simple_layout(ui_context *cx, ui_element_frame_data *elm } static void _ui_compute_children_layout(ui_context *cx, ui_element_frame_data *elm) { - uint32_t child_size[2] = { 0 }; + uint32_t child_size[2] = { 0, 0 }; // NOTE: the number of fills for the opposite axis of this box needs to be 1 // because it will never get incremented in the loop below and cause a divide by zero // and the number of fills for the axis of the box needs to start at zero or else it will // be n+1 causing incorrect sizes - uint32_t num_fills[2] = { 1 }; + uint32_t num_fills[2] = { 1, 1 }; num_fills[elm->size.axis] = 0; // TODO: maybe just use the actual data instead of copying? @@ -302,7 +302,9 @@ void ui_compute_layout(ui_context *cx, size_t element_index) { bool post_compute[2] = { false, false }; // only compute layout for children of root if (elm->index > 0) { - _ui_compute_simple_layout(cx, elm, axis, post_compute); + for (int i=0; i<2; ++i) { + _ui_compute_simple_layout(cx, elm, i, post_compute); + } } _ui_compute_children_layout(cx, elm); @@ -382,14 +384,6 @@ void ui_update_cache(ui_context *cx, size_t element_index) { ui_update_cache(cx, child_index); } while ((child_index = _next(child_index)) < SIZE_MAX); } - - cx->frame_elements.size = 1; - cx->frame_elements.data[0].first = SIZE_MAX; - cx->frame_elements.data[0].prev = SIZE_MAX; - cx->frame_elements.data[0].next = SIZE_MAX; - cx->frame_elements.data[0].last = SIZE_MAX; - cx->frame_elements.data[0].parent = SIZE_MAX; - cx->current_parent = 0; } void ui_prune(ui_context *cx) { @@ -408,6 +402,13 @@ void ui_prune(ui_context *cx) { } cx->frame_index += 1; + cx->frame_elements.size = 1; + cx->frame_elements.data[0].first = SIZE_MAX; + cx->frame_elements.data[0].prev = SIZE_MAX; + cx->frame_elements.data[0].next = SIZE_MAX; + cx->frame_elements.data[0].last = SIZE_MAX; + cx->frame_elements.data[0].parent = SIZE_MAX; + cx->current_parent = 0; } #endif