29#include <Objectively/Vector.h>
30#include <ObjectivelyGPU/CopyPass.h>
31#include <ObjectivelyGPU/Mathlib.h>
32#include <ObjectivelyGPU/RenderPass.h>
38#include "../Assets/Renderer.frag.dxil.h"
39#include "../Assets/Renderer.frag.metal.h"
40#include "../Assets/Renderer.frag.spv.h"
41#include "../Assets/Renderer.vert.dxil.h"
42#include "../Assets/Renderer.vert.metal.h"
43#include "../Assets/Renderer.vert.spv.h"
52 if (!strcmp(
"Renderer.frag.dxil",
name)) {
53 data = $(alloc(Data), initWithConstMemory, Renderer_frag_dxil, Renderer_frag_dxil_len);
54 }
else if (!strcmp(
"Renderer.frag.metal",
name)) {
55 data = $(alloc(Data), initWithConstMemory, Renderer_frag_metal, Renderer_frag_metal_len);
56 }
else if (!strcmp(
"Renderer.frag.spv",
name)) {
57 data = $(alloc(Data), initWithConstMemory, Renderer_frag_spv, Renderer_frag_spv_len);
58 }
else if (!strcmp(
"Renderer.vert.dxil",
name)) {
59 data = $(alloc(Data), initWithConstMemory, Renderer_vert_dxil, Renderer_vert_dxil_len);
60 }
else if (!strcmp(
"Renderer.vert.metal",
name)) {
61 data = $(alloc(Data), initWithConstMemory, Renderer_vert_metal, Renderer_vert_metal_len);
62 }
else if (!strcmp(
"Renderer.vert.spv",
name)) {
63 data = $(alloc(Data), initWithConstMemory, Renderer_vert_spv, Renderer_vert_spv_len);
69#define _Class _Renderer
80 this->commands = NULL;
83 release(this->sampler);
84 release(this->vertexBuffer);
85 release(this->transferBuffer);
86 release(this->pipeline);
88 release(this->vertices);
89 release(this->drawArrays);
90 release(this->device);
95#pragma mark - Renderer
114 self->commands = commands;
115 self->framebuffer = framebuffer;
117 $(self->vertices, removeAll);
118 $(self->drawArrays, removeAll);
120 self->scissor = MakeRect(0, 0, framebuffer->size.w, framebuffer->size.h);
130 const float x1 = rect->x, y1 = rect->y;
131 const float x2 = rect->x + rect->w, y2 = rect->y + rect->h;
133 const float u1 = uv ? uv->x : 0.0f, v1 = uv ? uv->y : 0.0f;
134 const float u2 = uv ? uv->x + uv->w : 0.0f, v2 = uv ? uv->y + uv->h : 0.0f;
136 const vec4 shapeRect = { { (x1 + x2) * 0.5f, (y1 + y2) * 0.5f, rect->w * 0.5f, rect->h * 0.5f } };
137 const vec2 shape = { { (float) radius, (
float) stroke } };
139 verts[0] = (
MVC_Vertex) { { { x1, y1 } }, { { u1, v1 } }, { 0 }, shapeRect, shape };
140 verts[1] = (
MVC_Vertex) { { { x2, y1 } }, { { u2, v1 } }, { 0 }, shapeRect, shape };
141 verts[2] = (
MVC_Vertex) { { { x1, y2 } }, { { u1, v2 } }, { 0 }, shapeRect, shape };
142 verts[3] = (
MVC_Vertex) { { { x2, y1 } }, { { u2, v1 } }, { 0 }, shapeRect, shape };
143 verts[4] = (
MVC_Vertex) { { { x2, y2 } }, { { u2, v2 } }, { 0 }, shapeRect, shape };
144 verts[5] = (
MVC_Vertex) { { { x1, y2 } }, { { u1, v2 } }, { 0 }, shapeRect, shape };
153 if (src == NULL ||
texture == NULL) {
154 return (SDL_FRect) { 0.0f, 0.0f, 1.0f, 1.0f };
157 const float w = (float)
texture->size.w, h = (
float)
texture->size.h;
159 return (SDL_FRect) { src->x / w, src->y / h, src->w / w, src->h / h };
171 const SDL_Color *from,
const SDL_Color *to) {
173 const float radians = float_radians((
float) angle);
174 const float dx = sinf(radians), dy = cosf(radians);
180 const float extent = fabsf(
bounds->w * dx) + fabsf(
bounds->h * dy);
183 if (extent > 0.001f) {
184 t = 0.5f + ((x - cx) * dx + (y - cy) * dy) / extent;
185 t = t < 0.f ? 0.f : (t > 1.f ? 1.f : t);
189 (Uint8) (from->r + (to->r - from->r) * t + 0.5f),
190 (Uint8) (from->g + (to->g - from->g) * t + 0.5f),
191 (Uint8) (from->b + (to->b - from->b) * t + 0.5f),
192 (Uint8) (from->a + (to->a - from->a) * t + 0.5f),
203 bool closed,
float width,
const SDL_Color *color) {
205 const size_t segments = closed ? count : count - 1;
208 const size_t batchSize = lengthof(verts) / 6;
210 for (
size_t s = 0; s < segments; ) {
212 const size_t batch = min(segments - s, batchSize);
214 for (
size_t i = 0; i < batch; i++, s++) {
216 const SDL_Point *a = &points[s], *b = &points[(s + 1) % count];
218 const float ax = (float) a->x, ay = (
float) a->y;
219 const float bx = (float) b->x, by = (
float) b->y;
221 const float dx = bx - ax, dy = by - ay;
222 const float len = sqrtf(dx * dx + dy * dy);
224 float nx = 0.0f, ny = 0.0f;
226 nx = (-dy / len) * width * 0.5f;
227 ny = ( dx / len) * width * 0.5f;
231 v[0] = (
MVC_Vertex) { { { ax - nx, ay - ny } }, { { 0.0f, 0.0f } }, { 0 } };
232 v[1] = (
MVC_Vertex) { { { ax + nx, ay + ny } }, { { 0.0f, 0.0f } }, { 0 } };
233 v[2] = (
MVC_Vertex) { { { bx - nx, by - ny } }, { { 0.0f, 0.0f } }, { 0 } };
234 v[3] = (
MVC_Vertex) { { { ax + nx, ay + ny } }, { { 0.0f, 0.0f } }, { 0 } };
235 v[4] = (
MVC_Vertex) { { { bx + nx, by + ny } }, { { 0.0f, 0.0f } }, { 0 } };
236 v[5] = (
MVC_Vertex) { { { bx - nx, by - ny } }, { { 0.0f, 0.0f } }, { 0 } };
248 const SDL_Color *topLeft,
const SDL_Color *bottomRight) {
253 SDL_RectToFRect(rect, &frect);
266static void drawLine(
const Renderer *self,
const SDL_Point *points,
const SDL_Color *color) {
277static void drawLines(
const Renderer *self,
const SDL_Point *points,
size_t count,
const SDL_Color *color) {
294 const SDL_Color *color) {
299 if (count < 3 || width < 1) {
311 int angle,
const SDL_Color *from,
const SDL_Color *to) {
320 SDL_FRect
bounds = { (float) points[0].x, (
float) points[0].y, 0.f, 0.f };
323 for (
size_t i = 1; i < count; i++) {
326 x2 = max(x2, (
float) points[i].x);
327 y2 = max(y2, (
float) points[i].y);
334 const size_t triangles = count - 2;
337 const size_t batchSize = lengthof(verts) / 3;
339 for (
size_t t = 0; t < triangles; ) {
341 const size_t batch = min(triangles - t, batchSize);
343 for (
size_t i = 0; i < batch; i++, t++) {
345 const SDL_Point *p[3] = { &points[0], &points[t + 1], &points[t + 2] };
347 for (
size_t j = 0; j < 3; j++) {
349 const float x = (float) p[j]->x, y = (
float) p[j]->y;
351 verts[i * 3 + j] = (
MVC_Vertex) { { { x, y } }, { { 0.f, 0.f } },
369 const SDL_Point points[5] = {
370 { rect->x, rect->y },
371 { rect->x + rect->w, rect->y },
372 { rect->x + rect->w, rect->y + rect->h },
373 { rect->x, rect->y + rect->h },
374 { rect->x, rect->y },
388 const float x1 = (float) rect->x, y1 = (
float) rect->y;
389 const float x2 = (float) rect->x + rect->w, y2 = (
float) rect->y + rect->h;
392 { { { x1, y1 } }, { { 0.0f, 0.0f } }, { 0 } },
393 { { { x2, y1 } }, { { 0.0f, 0.0f } }, { 0 } },
394 { { { x1, y2 } }, { { 0.0f, 0.0f } }, { 0 } },
395 { { { x2, y1 } }, { { 0.0f, 0.0f } }, { 0 } },
396 { { { x2, y2 } }, { { 0.0f, 0.0f } }, { 0 } },
397 { { { x1, y2 } }, { { 0.0f, 0.0f } }, { 0 } },
412 SDL_RectToFRect(rect, &frect);
429 SDL_RectToFRect(rect, &frect);
442 int angle,
const SDL_Color *from,
const SDL_Color *to) {
449 SDL_RectToFRect(rect, &frect);
454 for (
size_t i = 0; i < lengthof(verts); i++) {
501 const float x1 = dest->x, y1 = dest->y;
502 const float x2 = dest->x + dest->w, y2 = dest->y + dest->h;
506 const float u1 = uv.x, v1 = uv.y;
507 const float u2 = uv.x + uv.w, v2 = uv.y + uv.h;
510 { { { x1, y1 } }, { { u1, v1 } }, { 0 } },
511 { { { x2, y1 } }, { { u2, v1 } }, { 0 } },
512 { { { x1, y2 } }, { { u1, v2 } }, { 0 } },
513 { { { x2, y1 } }, { { u2, v1 } }, { 0 } },
514 { { { x2, y2 } }, { { u2, v2 } }, { 0 } },
515 { { { x1, y2 } }, { { u1, v2 } }, { 0 } },
557 Framebuffer *framebuffer = self->framebuffer;
560 const SDL_GPUColorTargetInfo colorTarget = $(framebuffer, colorTargetInfo, 0, SDL_GPU_LOADOP_LOAD, SDL_GPU_STOREOP_STORE);
562 const size_t vertexCount = self->vertices->count;
564 CopyPass *copyPass = $(self->commands, beginCopyPass);
566 if (vertexCount > 0) {
567 const Uint32 vertexSize = (Uint32) (vertexCount *
sizeof(
MVC_Vertex));
569 if (vertexCount > self->vertexBufferCapacity) {
570 release(self->vertexBuffer);
571 self->vertexBuffer = $(self->
device, createBuffer, &(SDL_GPUBufferCreateInfo) {
572 .usage = SDL_GPU_BUFFERUSAGE_VERTEX,
576 release(self->transferBuffer);
577 self->transferBuffer = $(self->
device, createTransferBuffer, &(SDL_GPUTransferBufferCreateInfo) {
578 .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
582 self->vertexBufferCapacity = (Uint32) vertexCount;
585 $(self->transferBuffer, write, self->vertices->elements, vertexSize,
true);
587 $(copyPass, uploadBuffer,
588 &(SDL_GPUTransferBufferLocation) { .transfer_buffer = self->transferBuffer->buffer },
589 &(SDL_GPUBufferRegion) { .buffer = self->vertexBuffer->buffer, .size = vertexSize },
595 RenderPass *renderPass = $(self->commands, beginRenderPass, &colorTarget, 1, NULL);
597 $(renderPass, setViewport, &(SDL_GPUViewport){
598 .x = 0.0f, .y = 0.0f,
599 .w = (float) framebuffer->size.w, .h = (
float) framebuffer->size.h,
600 .min_depth = 0.0f, .max_depth = 1.0f,
604 SDL_GetWindowSize(self->
device->window, &winW, &winH);
605 const mat4 projection = mat4_ortho(0.f, (
float) winW, (
float) winH, 0.f, -1.f, 1.f);
606 $(self->commands, pushVertexUniformData, 0, projection.f,
sizeof(projection));
608 $(renderPass, bindPipeline, self->pipeline);
614 $(renderPass, bindVertexBuffers, 0, &(SDL_GPUBufferBinding) { .buffer = self->vertexBuffer->buffer }, 1);
616 for (
size_t i = 0; i < self->drawArrays->count; i++) {
617 const MVC_DrawArrays *
draw = VectorElement(self->drawArrays, MVC_DrawArrays, i);
619 $(renderPass, setScissor, &
draw->scissor);
621 $(renderPass, bindFragmentSamplers, 0, &(SDL_GPUTextureSamplerBinding) {
622 .texture =
draw->texture->texture, .sampler = self->sampler->sampler,
625 $(renderPass, drawPrimitives,
draw->vertexCount, 1,
draw->firstVertex, 0);
631 self->commands = NULL;
632 self->framebuffer = NULL;
643 self->
device = retain(device);
646 self->vertices = $(alloc(Vector), initWithSize,
sizeof(
MVC_Vertex));
647 assert(self->vertices);
649 self->drawArrays = $(alloc(Vector), initWithSize,
sizeof(MVC_DrawArrays));
650 assert(self->drawArrays);
664 const MVC_DrawArrays
draw = {
665 .firstVertex = (Uint32) self->vertices->count,
666 .vertexCount = (Uint32) count,
668 .scissor = self->scissor,
671 Vector *vertices = self->vertices;
673 if (vertices->count + count > vertices->capacity) {
674 $(vertices,
resize, max(vertices->capacity * 2, vertices->count + count));
678 for (
size_t i = 0; i < count; i++) {
681 out[i].
color = *color;
685 vertices->count += count;
689 if (self->drawArrays->count) {
690 MVC_DrawArrays *last = VectorElement(self->drawArrays, MVC_DrawArrays, self->drawArrays->count - 1);
691 if (last->texture ==
draw.texture && SDL_RectsEqual(&last->scissor, &
draw.scissor)) {
692 last->vertexCount +=
draw.vertexCount;
694 $(self->drawArrays, add, (MVC_DrawArrays *) &
draw);
697 $(self->drawArrays, add, (MVC_DrawArrays *) &
draw);
707 Shader *vertexShader = $(self->
device, loadShader,
"Renderer.vert", &(SDL_GPUShaderCreateInfo) {
708 .stage = SDL_GPU_SHADERSTAGE_VERTEX,
709 .num_uniform_buffers = 1,
712 Shader *fragmentShader = $(self->
device, loadShader,
"Renderer.frag", &(SDL_GPUShaderCreateInfo) {
713 .stage = SDL_GPU_SHADERSTAGE_FRAGMENT,
717 const SDL_GPUVertexBufferDescription vertexBufferInfo = {
720 .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX,
721 .instance_step_rate = 0,
724 const SDL_GPUColorTargetDescription colorTarget = {
725 .format = $(self->
device, getSwapchainTextureFormat),
727 .enable_blend =
true,
728 .color_blend_op = SDL_GPU_BLENDOP_ADD,
729 .alpha_blend_op = SDL_GPU_BLENDOP_ADD,
730 .src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
731 .dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
732 .src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE,
733 .dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
737 const SDL_GPUGraphicsPipelineCreateInfo pipelineInfo = {
738 .vertex_shader = vertexShader->shader,
739 .fragment_shader = fragmentShader->shader,
740 .vertex_input_state = {
741 .vertex_buffer_descriptions = &vertexBufferInfo,
742 .num_vertex_buffers = 1,
743 .vertex_attributes = (SDL_GPUVertexAttribute[]) {
747 .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
753 .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
759 .format = SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4_NORM,
765 .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4,
771 .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
775 .num_vertex_attributes = 5,
777 .primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
778 .rasterizer_state = {
779 .fill_mode = SDL_GPU_FILLMODE_FILL,
780 .cull_mode = SDL_GPU_CULLMODE_NONE,
781 .front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE,
784 .color_target_descriptions = &colorTarget,
785 .num_color_targets = 1,
789 self->pipeline = $(self->
device, createGraphicsPipeline, &pipelineInfo);
791 release(vertexShader);
792 release(fragmentShader);
794 self->sampler = $(self->
device, createSampler, &(
const SDL_GPUSamplerCreateInfo) {
795 .min_filter = SDL_GPU_FILTER_LINEAR,
796 .mag_filter = SDL_GPU_FILTER_LINEAR,
797 .mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR,
798 .address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
799 .address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
800 .address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
803 const Uint8 white[4] = { 255, 255, 255, 255 };
805 self->white = $(self->
device, createTexture, &(
const SDL_GPUTextureCreateInfo) {
806 .type = SDL_GPU_TEXTURETYPE_2D,
807 .format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
808 .usage = SDL_GPU_TEXTUREUSAGE_SAMPLER,
811 .layer_count_or_depth = 1,
822 self->commands = NULL;
824 self->white = release(self->white);
825 self->sampler = release(self->sampler);
827 self->vertexBuffer = release(self->vertexBuffer);
828 self->transferBuffer = release(self->transferBuffer);
829 self->vertexBufferCapacity = 0;
831 self->pipeline = release(self->pipeline);
833 $(self->vertices, removeAll);
834 $(self->drawArrays, removeAll);
846 self->scissor = MakeRect(0, 0, self->framebuffer->size.w, self->framebuffer->size.h);
850#pragma mark - Class lifecycle
857 ((ObjectInterface *) clazz->interface)->dealloc =
dealloc;
859 ((RendererInterface *) clazz->interface)->beginFrame =
beginFrame;
860 ((RendererInterface *) clazz->interface)->beginFrameWith =
beginFrameWith;
861 ((RendererInterface *) clazz->interface)->drawBevel =
drawBevel;
862 ((RendererInterface *) clazz->interface)->drawLine =
drawLine;
863 ((RendererInterface *) clazz->interface)->drawLines =
drawLines;
864 ((RendererInterface *) clazz->interface)->drawPolygon =
drawPolygon;
866 ((RendererInterface *) clazz->interface)->drawRect =
drawRect;
867 ((RendererInterface *) clazz->interface)->drawRectFilled =
drawRectFilled;
868 ((RendererInterface *) clazz->interface)->drawRoundedRect =
drawRoundedRect;
873 ((RendererInterface *) clazz->interface)->drawTexture =
drawTexture;
875 ((RendererInterface *) clazz->interface)->drawView =
drawView;
876 ((RendererInterface *) clazz->interface)->endFrame =
endFrame;
877 ((RendererInterface *) clazz->interface)->initWithDevice =
initWithDevice;
881 ((RendererInterface *) clazz->interface)->setClippingFrame =
setClippingFrame;
895 clazz = _initialize(&(
const ClassDef) {
897 .superclass = _Object(),
899 .interfaceSize =
sizeof(RendererInterface),
static View * init(View *self)
static void render(View *self, Renderer *renderer)
static String * name(const Font *self)
static Texture * texture(ImageAtlas *self, RenderDevice *device)
static void drawTextureRegion(const Renderer *self, Texture *texture, const SDL_Rect *src, const SDL_FRect *dest, const SDL_Color *color)
static void drawView(Renderer *self, View *view)
static Data * shaderResourceProvider(const char *name)
ResourceProvider for loading shaders from xxd headers.
static SDL_FRect textureRegion(const Texture *texture, const SDL_Rect *src)
Resolves the normalized texture coordinates of src within texture, or of the whole texture when src i...
static void drawLines(const Renderer *self, const SDL_Point *points, size_t count, const SDL_Color *color)
static void drawTexture(const Renderer *self, Texture *texture, const SDL_FRect *dest, const SDL_Color *color)
static void drawRoundedRectGradientFilled(const Renderer *self, const SDL_Rect *rect, int radius, int angle, const SDL_Color *from, const SDL_Color *to)
static void setClippingFrame(Renderer *self, const SDL_Rect *clippingFrame)
static void drawPolygon(const Renderer *self, const SDL_Point *points, size_t count, int width, const SDL_Color *color)
static void beginFrame(Renderer *self)
static void drawRoundedRect(const Renderer *self, const SDL_Rect *rect, int radius, int width, const SDL_Color *color)
static void drawRectFilled(const Renderer *self, const SDL_Rect *rect, const SDL_Color *color)
static void drawRoundedTextureRegion(const Renderer *self, Texture *texture, const SDL_Rect *src, const SDL_FRect *dest, int radius, const SDL_Color *color)
static void drawRect(const Renderer *self, const SDL_Rect *rect, const SDL_Color *color)
static Renderer * initWithDevice(Renderer *self, RenderDevice *device)
static void drawRoundedRectFilled(const Renderer *self, const SDL_Rect *rect, int radius, const SDL_Color *color)
static void drawPolygonFilled(const Renderer *self, const SDL_Point *points, size_t count, int angle, const SDL_Color *from, const SDL_Color *to)
static void pushDrawArrays(const Renderer *self, const MVC_Vertex *verts, size_t count, Texture *texture, const SDL_Color *color)
static void strokePolyline(const Renderer *self, const SDL_Point *points, size_t count, bool closed, float width, const SDL_Color *color)
Appends the quads stroking points, of width, optionally closing the loop.
static void dealloc(Object *self)
static SDL_Color gradientColor(float x, float y, const SDL_FRect *bounds, int angle, const SDL_Color *from, const SDL_Color *to)
The color at x, y for a gradient of angle degrees across bounds.
static void drawLine(const Renderer *self, const SDL_Point *points, const SDL_Color *color)
static void roundedRectVertices(MVC_Vertex *verts, const SDL_FRect *rect, int radius, int stroke, const SDL_FRect *uv)
Fills six vertices covering rect as two triangles, tagged so the fragment shader shades them as a rou...
static void endFrame(Renderer *self)
static void drawBevel(const Renderer *self, const SDL_Rect *rect, int radius, int width, const SDL_Color *topLeft, const SDL_Color *bottomRight)
static void initialize(Class *clazz)
static void beginFrameWith(Renderer *self, CommandBuffer *commands, Framebuffer *framebuffer)
static void renderDeviceDidReset(Renderer *self)
static void drawRoundedTexture(const Renderer *self, Texture *texture, const SDL_FRect *dest, int radius, const SDL_Color *color)
static void renderDeviceWillReset(Renderer *self)
Renderer extends Object with ObjectivelyMVC's UI rendering layer.
static void resize(View *self, const SDL_Size *size)
static void draw(View *self, Renderer *renderer)
static SDL_Rect clippingFrame(const View *self)
static SDL_Rect bounds(const View *self)
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
SDL_Rect MVC_TransformToWindow(SDL_Window *window, const SDL_Rect *rect)
Transforms the specified rectangle to normalized device coordinates in window.
Interleaved position + texcoord + color + rounded rect shape vertex for GPU upload.
Renderer extends Object with ObjectivelyMVC's UI rendering layer.
RenderDevice * device
The backing RenderDevice.
void pushDrawArrays(const Renderer *self, const MVC_Vertex *verts, size_t count, Texture *texture, const SDL_Color *color)
Appends raw vertices and a draw call record to the frame queue.
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
bool clipsSubviews
If true, subviews will be clipped to this View's frame.
View * superview
The super View.