ObjectivelyMVC
Object oriented MVC framework for SDL3 and GNU C
Loading...
Searching...
No Matches
Renderer.c
Go to the documentation of this file.
1/*
2 * ObjectivelyMVC: Object oriented MVC framework for SDL3 and C.
3 * Copyright (C) 2014 Jay Dolan <jay@jaydolan.com>
4 *
5 * This software is provided 'as-is', without any express or implied
6 * warranty. In no event will the authors be held liable for any damages
7 * arising from the use of this software.
8 *
9 * Permission is granted to anyone to use this software for any purpose,
10 * including commercial applications, and to alter it and redistribute it
11 * freely, subject to the following restrictions:
12 *
13 * 1. The origin of this software must not be misrepresented; you must not
14 * claim that you wrote the original software. If you use this software
15 * in a product, an acknowledgment in the product documentation would be
16 * appreciated but is not required.
17 *
18 * 2. Altered source versions must be plainly marked as such, and must not be
19 * misrepresented as being the original software.
20 *
21 * 3. This notice may not be removed or altered from any source distribution.
22 */
23
24#include <assert.h>
25#include <math.h>
26#include <stdlib.h>
27#include <string.h>
28
29#include <Objectively/Vector.h>
30#include <ObjectivelyGPU/CopyPass.h>
31#include <ObjectivelyGPU/Mathlib.h>
32#include <ObjectivelyGPU/RenderPass.h>
33
34#include "Renderer.h"
35#include "View.h"
36#include "Window.h"
37
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"
44
48static Data *shaderResourceProvider(const char *name) {
49
50 Data *data = NULL;
51
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);
64 }
65
66 return data;
67}
68
69#define _Class _Renderer
70
71#pragma mark - Object
72
76static void dealloc(Object *self) {
77
78 Renderer *this = (Renderer *) self;
79
80 this->commands = NULL;
81
82 release(this->white);
83 release(this->sampler);
84 release(this->vertexBuffer);
85 release(this->transferBuffer);
86 release(this->pipeline);
87
88 release(this->vertices);
89 release(this->drawArrays);
90 release(this->device);
91
92 super(Object, self, dealloc);
93}
94
95#pragma mark - Renderer
96
101static void beginFrame(Renderer *self) {
102 $(self, beginFrameWith, self->device->commands, self->device->framebuffer);
103}
104
109static void beginFrameWith(Renderer *self, CommandBuffer *commands, Framebuffer *framebuffer) {
110
111 assert(commands);
112 assert(framebuffer);
113
114 self->commands = commands;
115 self->framebuffer = framebuffer;
116
117 $(self->vertices, removeAll);
118 $(self->drawArrays, removeAll);
119
120 self->scissor = MakeRect(0, 0, framebuffer->size.w, framebuffer->size.h);
121}
122
128static void roundedRectVertices(MVC_Vertex *verts, const SDL_FRect *rect, int radius, int stroke, const SDL_FRect *uv) {
129
130 const float x1 = rect->x, y1 = rect->y;
131 const float x2 = rect->x + rect->w, y2 = rect->y + rect->h;
132
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;
135
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 } };
138
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 };
145}
146
151static SDL_FRect textureRegion(const Texture *texture, const SDL_Rect *src) {
152
153 if (src == NULL || texture == NULL) {
154 return (SDL_FRect) { 0.0f, 0.0f, 1.0f, 1.0f };
155 }
156
157 const float w = (float) texture->size.w, h = (float) texture->size.h;
158
159 return (SDL_FRect) { src->x / w, src->y / h, src->w / w, src->h / h };
160}
161
170static SDL_Color gradientColor(float x, float y, const SDL_FRect *bounds, int angle,
171 const SDL_Color *from, const SDL_Color *to) {
172
173 const float radians = float_radians((float) angle);
174 const float dx = sinf(radians), dy = cosf(radians);
175
176 const float cx = bounds->x + bounds->w * 0.5f;
177 const float cy = bounds->y + bounds->h * 0.5f;
178
179 // the extent of the bounds along the axis, which is the box's support in that direction
180 const float extent = fabsf(bounds->w * dx) + fabsf(bounds->h * dy);
181
182 float t = 0.5f;
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);
186 }
187
188 return (SDL_Color) {
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),
193 };
194}
195
202static void strokePolyline(const Renderer *self, const SDL_Point *points, size_t count,
203 bool closed, float width, const SDL_Color *color) {
204
205 const size_t segments = closed ? count : count - 1;
206
207 MVC_Vertex verts[16 * 6];
208 const size_t batchSize = lengthof(verts) / 6;
209
210 for (size_t s = 0; s < segments; ) {
211
212 const size_t batch = min(segments - s, batchSize);
213
214 for (size_t i = 0; i < batch; i++, s++) {
215
216 const SDL_Point *a = &points[s], *b = &points[(s + 1) % count];
217
218 const float ax = (float) a->x, ay = (float) a->y;
219 const float bx = (float) b->x, by = (float) b->y;
220
221 const float dx = bx - ax, dy = by - ay;
222 const float len = sqrtf(dx * dx + dy * dy);
223
224 float nx = 0.0f, ny = 0.0f;
225 if (len > 0.001f) {
226 nx = (-dy / len) * width * 0.5f;
227 ny = ( dx / len) * width * 0.5f;
228 }
229
230 MVC_Vertex *v = &verts[i * 6];
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 } };
237 }
238
239 $(self, pushDrawArrays, verts, batch * 6, NULL, color);
240 }
241}
242
247static void drawBevel(const Renderer *self, const SDL_Rect *rect, int radius, int width,
248 const SDL_Color *topLeft, const SDL_Color *bottomRight) {
249
250 assert(rect);
251
252 SDL_FRect frect;
253 SDL_RectToFRect(rect, &frect);
254
255 MVC_Vertex verts[6];
256 roundedRectVertices(verts, &frect, radius, width, NULL);
257
258 $(self, pushDrawArrays, verts, 3, NULL, topLeft);
259 $(self, pushDrawArrays, verts + 3, 3, NULL, bottomRight);
260}
261
266static void drawLine(const Renderer *self, const SDL_Point *points, const SDL_Color *color) {
267
268 assert(points);
269
270 $(self, drawLines, points, 2, color);
271}
272
277static void drawLines(const Renderer *self, const SDL_Point *points, size_t count, const SDL_Color *color) {
278
279 assert(points);
280 assert(color);
281
282 if (count < 2) {
283 return;
284 }
285
286 strokePolyline(self, points, count, false, 1.f, color);
287}
288
293static void drawPolygon(const Renderer *self, const SDL_Point *points, size_t count, int width,
294 const SDL_Color *color) {
295
296 assert(points);
297 assert(color);
298
299 if (count < 3 || width < 1) {
300 return;
301 }
302
303 strokePolyline(self, points, count, true, (float) width, color);
304}
305
310static void drawPolygonFilled(const Renderer *self, const SDL_Point *points, size_t count,
311 int angle, const SDL_Color *from, const SDL_Color *to) {
312
313 assert(points);
314 assert(from);
315
316 if (count < 3) {
317 return;
318 }
319
320 SDL_FRect bounds = { (float) points[0].x, (float) points[0].y, 0.f, 0.f };
321 float x2 = bounds.x, y2 = bounds.y;
322
323 for (size_t i = 1; i < count; i++) {
324 bounds.x = min(bounds.x, (float) points[i].x);
325 bounds.y = min(bounds.y, (float) points[i].y);
326 x2 = max(x2, (float) points[i].x);
327 y2 = max(y2, (float) points[i].y);
328 }
329
330 bounds.w = x2 - bounds.x;
331 bounds.h = y2 - bounds.y;
332
333 // a convex polygon triangulates as a fan from its first vertex
334 const size_t triangles = count - 2;
335
336 MVC_Vertex verts[16 * 3];
337 const size_t batchSize = lengthof(verts) / 3;
338
339 for (size_t t = 0; t < triangles; ) {
340
341 const size_t batch = min(triangles - t, batchSize);
342
343 for (size_t i = 0; i < batch; i++, t++) {
344
345 const SDL_Point *p[3] = { &points[0], &points[t + 1], &points[t + 2] };
346
347 for (size_t j = 0; j < 3; j++) {
348
349 const float x = (float) p[j]->x, y = (float) p[j]->y;
350
351 verts[i * 3 + j] = (MVC_Vertex) { { { x, y } }, { { 0.f, 0.f } },
352 to ? gradientColor(x, y, &bounds, angle, from, to) : *from
353 };
354 }
355 }
356
357 $(self, pushDrawArrays, verts, batch * 3, NULL, NULL);
358 }
359}
360
365static void drawRect(const Renderer *self, const SDL_Rect *rect, const SDL_Color *color) {
366
367 assert(rect);
368
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 },
375 };
376
377 $(self, drawLines, points, 5, color);
378}
379
384static void drawRectFilled(const Renderer *self, const SDL_Rect *rect, const SDL_Color *color) {
385
386 assert(rect);
387
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;
390
391 const MVC_Vertex verts[6] = {
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 } },
398 };
399
400 $(self, pushDrawArrays, verts, 6, NULL, color);
401}
402
407static void drawRoundedRect(const Renderer *self, const SDL_Rect *rect, int radius, int width, const SDL_Color *color) {
408
409 assert(rect);
410
411 SDL_FRect frect;
412 SDL_RectToFRect(rect, &frect);
413
414 MVC_Vertex verts[6];
415 roundedRectVertices(verts, &frect, radius, width, NULL);
416
417 $(self, pushDrawArrays, verts, 6, NULL, color);
418}
419
424static void drawRoundedRectFilled(const Renderer *self, const SDL_Rect *rect, int radius, const SDL_Color *color) {
425
426 assert(rect);
427
428 SDL_FRect frect;
429 SDL_RectToFRect(rect, &frect);
430
431 MVC_Vertex verts[6];
432 roundedRectVertices(verts, &frect, radius, 0, NULL);
433
434 $(self, pushDrawArrays, verts, 6, NULL, color);
435}
436
441static void drawRoundedRectGradientFilled(const Renderer *self, const SDL_Rect *rect, int radius,
442 int angle, const SDL_Color *from, const SDL_Color *to) {
443
444 assert(rect);
445 assert(from);
446 assert(to);
447
448 SDL_FRect frect;
449 SDL_RectToFRect(rect, &frect);
450
451 MVC_Vertex verts[6];
452 roundedRectVertices(verts, &frect, radius, 0, NULL);
453
454 for (size_t i = 0; i < lengthof(verts); i++) {
455 verts[i].color = gradientColor(verts[i].position.x, verts[i].position.y, &frect, angle, from, to);
456 }
457
458 $(self, pushDrawArrays, verts, lengthof(verts), NULL, NULL);
459}
460
465static void drawRoundedTexture(const Renderer *self, Texture *texture, const SDL_FRect *dest, int radius, const SDL_Color *color) {
466 $(self, drawRoundedTextureRegion, texture, NULL, dest, radius, color);
467}
468
473static void drawRoundedTextureRegion(const Renderer *self, Texture *texture, const SDL_Rect *src, const SDL_FRect *dest, int radius, const SDL_Color *color) {
474
475 assert(dest);
476
477 const SDL_FRect uv = textureRegion(texture, src);
478
479 MVC_Vertex verts[6];
480 roundedRectVertices(verts, dest, radius, 0, &uv);
481
482 $(self, pushDrawArrays, verts, 6, texture, color);
483}
484
489static void drawTexture(const Renderer *self, Texture *texture, const SDL_FRect *dest, const SDL_Color *color) {
490 $(self, drawTextureRegion, texture, NULL, dest, color);
491}
492
497static void drawTextureRegion(const Renderer *self, Texture *texture, const SDL_Rect *src, const SDL_FRect *dest, const SDL_Color *color) {
498
499 assert(dest);
500
501 const float x1 = dest->x, y1 = dest->y;
502 const float x2 = dest->x + dest->w, y2 = dest->y + dest->h;
503
504 const SDL_FRect uv = textureRegion(texture, src);
505
506 const float u1 = uv.x, v1 = uv.y;
507 const float u2 = uv.x + uv.w, v2 = uv.y + uv.h;
508
509 const MVC_Vertex verts[6] = {
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 } },
516 };
517
518 $(self, pushDrawArrays, verts, 6, texture, color);
519}
520
525static void drawView(Renderer *self, View *view) {
526
527 assert(view);
528
529 const SDL_Rect clippingFrame = $(view, clippingFrame);
530 if (clippingFrame.w && clippingFrame.h) {
531
532 // The scissor enforces clipsSubviews, so it comes from the nearest clipping ancestor
533 // rather than from this View's own frame: siblings then share a scissor, which is one of
534 // the two conditions under which pushDrawArrays merges their draw calls.
535 const View *superview = view->superview;
536 while (superview && !superview->clipsSubviews) {
537 superview = superview->superview;
538 }
539
540 if (superview) {
541 const SDL_Rect scissor = $(superview, clippingFrame);
542 $(self, setClippingFrame, &scissor);
543 } else {
544 $(self, setClippingFrame, NULL);
545 }
546
547 $(view, render, self);
548 }
549}
550
555static void endFrame(Renderer *self) {
556
557 Framebuffer *framebuffer = self->framebuffer;
558 assert(framebuffer);
559
560 const SDL_GPUColorTargetInfo colorTarget = $(framebuffer, colorTargetInfo, 0, SDL_GPU_LOADOP_LOAD, SDL_GPU_STOREOP_STORE);
561
562 const size_t vertexCount = self->vertices->count;
563
564 CopyPass *copyPass = $(self->commands, beginCopyPass);
565
566 if (vertexCount > 0) {
567 const Uint32 vertexSize = (Uint32) (vertexCount * sizeof(MVC_Vertex));
568
569 if (vertexCount > self->vertexBufferCapacity) {
570 release(self->vertexBuffer);
571 self->vertexBuffer = $(self->device, createBuffer, &(SDL_GPUBufferCreateInfo) {
572 .usage = SDL_GPU_BUFFERUSAGE_VERTEX,
573 .size = vertexSize
574 });
575
576 release(self->transferBuffer);
577 self->transferBuffer = $(self->device, createTransferBuffer, &(SDL_GPUTransferBufferCreateInfo) {
578 .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
579 .size = vertexSize,
580 });
581
582 self->vertexBufferCapacity = (Uint32) vertexCount;
583 }
584
585 $(self->transferBuffer, write, self->vertices->elements, vertexSize, true);
586
587 $(copyPass, uploadBuffer,
588 &(SDL_GPUTransferBufferLocation) { .transfer_buffer = self->transferBuffer->buffer },
589 &(SDL_GPUBufferRegion) { .buffer = self->vertexBuffer->buffer, .size = vertexSize },
590 true);
591 }
592
593 release(copyPass);
594
595 RenderPass *renderPass = $(self->commands, beginRenderPass, &colorTarget, 1, NULL);
596
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,
601 });
602
603 int winW, winH;
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));
607
608 $(renderPass, bindPipeline, self->pipeline);
609
610 // the vertex buffer is created only once something has been drawn into the frame, so a
611 // frame that drew nothing has none to bind
612 if (vertexCount) {
613
614 $(renderPass, bindVertexBuffers, 0, &(SDL_GPUBufferBinding) { .buffer = self->vertexBuffer->buffer }, 1);
615
616 for (size_t i = 0; i < self->drawArrays->count; i++) {
617 const MVC_DrawArrays *draw = VectorElement(self->drawArrays, MVC_DrawArrays, i);
618
619 $(renderPass, setScissor, &draw->scissor);
620
621 $(renderPass, bindFragmentSamplers, 0, &(SDL_GPUTextureSamplerBinding) {
622 .texture = draw->texture->texture, .sampler = self->sampler->sampler,
623 }, 1);
624
625 $(renderPass, drawPrimitives, draw->vertexCount, 1, draw->firstVertex, 0);
626 }
627 }
628
629 release(renderPass);
630
631 self->commands = NULL;
632 self->framebuffer = NULL;
633}
634
639static Renderer *initWithDevice(Renderer *self, RenderDevice *device) {
640
641 self = (Renderer *) super(Object, self, init);
642 if (self) {
643 self->device = retain(device);
644 assert(self->device);
645
646 self->vertices = $(alloc(Vector), initWithSize, sizeof(MVC_Vertex));
647 assert(self->vertices);
648
649 self->drawArrays = $(alloc(Vector), initWithSize, sizeof(MVC_DrawArrays));
650 assert(self->drawArrays);
651 }
652
653 return self;
654}
655
660static void pushDrawArrays(const Renderer *self, const MVC_Vertex *verts, size_t count, Texture *texture, const SDL_Color *color) {
661
662 assert(verts);
663
664 const MVC_DrawArrays draw = {
665 .firstVertex = (Uint32) self->vertices->count,
666 .vertexCount = (Uint32) count,
667 .texture = texture ? texture : self->white,
668 .scissor = self->scissor,
669 };
670
671 Vector *vertices = self->vertices;
672
673 if (vertices->count + count > vertices->capacity) {
674 $(vertices, resize, max(vertices->capacity * 2, vertices->count + count));
675 }
676
677 MVC_Vertex *out = VectorElement(vertices, MVC_Vertex, vertices->count);
678 for (size_t i = 0; i < count; i++) {
679 out[i] = verts[i];
680 if (color) {
681 out[i].color = *color;
682 }
683 }
684
685 vertices->count += count;
686
687 // If this drawArrays is contiguous with the last one, combine them into a single call
688
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;
693 } else {
694 $(self->drawArrays, add, (MVC_DrawArrays *) &draw);
695 }
696 } else {
697 $(self->drawArrays, add, (MVC_DrawArrays *) &draw);
698 }
699}
700
705static void renderDeviceDidReset(Renderer *self) {
706
707 Shader *vertexShader = $(self->device, loadShader, "Renderer.vert", &(SDL_GPUShaderCreateInfo) {
708 .stage = SDL_GPU_SHADERSTAGE_VERTEX,
709 .num_uniform_buffers = 1,
710 });
711
712 Shader *fragmentShader = $(self->device, loadShader, "Renderer.frag", &(SDL_GPUShaderCreateInfo) {
713 .stage = SDL_GPU_SHADERSTAGE_FRAGMENT,
714 .num_samplers = 1,
715 });
716
717 const SDL_GPUVertexBufferDescription vertexBufferInfo = {
718 .slot = 0,
719 .pitch = sizeof(MVC_Vertex),
720 .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX,
721 .instance_step_rate = 0,
722 };
723
724 const SDL_GPUColorTargetDescription colorTarget = {
725 .format = $(self->device, getSwapchainTextureFormat),
726 .blend_state = {
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,
734 },
735 };
736
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[]) {
744 {
745 .location = 0,
746 .buffer_slot = 0,
747 .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
748 .offset = offsetof(MVC_Vertex, position)
749 },
750 {
751 .location = 1,
752 .buffer_slot = 0,
753 .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
754 .offset = offsetof(MVC_Vertex, uv)
755 },
756 {
757 .location = 2,
758 .buffer_slot = 0,
759 .format = SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4_NORM,
760 .offset = offsetof(MVC_Vertex, color)
761 },
762 {
763 .location = 3,
764 .buffer_slot = 0,
765 .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4,
766 .offset = offsetof(MVC_Vertex, rect)
767 },
768 {
769 .location = 4,
770 .buffer_slot = 0,
771 .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
772 .offset = offsetof(MVC_Vertex, shape)
773 },
774 },
775 .num_vertex_attributes = 5,
776 },
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,
782 },
783 .target_info = {
784 .color_target_descriptions = &colorTarget,
785 .num_color_targets = 1,
786 },
787 };
788
789 self->pipeline = $(self->device, createGraphicsPipeline, &pipelineInfo);
790
791 release(vertexShader);
792 release(fragmentShader);
793
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,
801 });
802
803 const Uint8 white[4] = { 255, 255, 255, 255 };
804
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,
809 .width = 1,
810 .height = 1,
811 .layer_count_or_depth = 1,
812 .num_levels = 1,
813 }, white);
814}
815
820static void renderDeviceWillReset(Renderer *self) {
821
822 self->commands = NULL;
823
824 self->white = release(self->white);
825 self->sampler = release(self->sampler);
826
827 self->vertexBuffer = release(self->vertexBuffer);
828 self->transferBuffer = release(self->transferBuffer);
829 self->vertexBufferCapacity = 0;
830
831 self->pipeline = release(self->pipeline);
832
833 $(self->vertices, removeAll);
834 $(self->drawArrays, removeAll);
835}
836
841static void setClippingFrame(Renderer *self, const SDL_Rect *clippingFrame) {
842
843 if (clippingFrame) {
844 self->scissor = MVC_TransformToWindow(self->device->window, clippingFrame);
845 } else {
846 self->scissor = MakeRect(0, 0, self->framebuffer->size.w, self->framebuffer->size.h);
847 }
848}
849
850#pragma mark - Class lifecycle
851
855static void initialize(Class *clazz) {
856
857 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
858
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;
865 ((RendererInterface *) clazz->interface)->drawPolygonFilled = drawPolygonFilled;
866 ((RendererInterface *) clazz->interface)->drawRect = drawRect;
867 ((RendererInterface *) clazz->interface)->drawRectFilled = drawRectFilled;
868 ((RendererInterface *) clazz->interface)->drawRoundedRect = drawRoundedRect;
869 ((RendererInterface *) clazz->interface)->drawRoundedRectFilled = drawRoundedRectFilled;
870 ((RendererInterface *) clazz->interface)->drawRoundedRectGradientFilled = drawRoundedRectGradientFilled;
871 ((RendererInterface *) clazz->interface)->drawRoundedTexture = drawRoundedTexture;
872 ((RendererInterface *) clazz->interface)->drawRoundedTextureRegion = drawRoundedTextureRegion;
873 ((RendererInterface *) clazz->interface)->drawTexture = drawTexture;
874 ((RendererInterface *) clazz->interface)->drawTextureRegion = drawTextureRegion;
875 ((RendererInterface *) clazz->interface)->drawView = drawView;
876 ((RendererInterface *) clazz->interface)->endFrame = endFrame;
877 ((RendererInterface *) clazz->interface)->initWithDevice = initWithDevice;
878 ((RendererInterface *) clazz->interface)->pushDrawArrays = pushDrawArrays;
879 ((RendererInterface *) clazz->interface)->renderDeviceDidReset = renderDeviceDidReset;
880 ((RendererInterface *) clazz->interface)->renderDeviceWillReset = renderDeviceWillReset;
881 ((RendererInterface *) clazz->interface)->setClippingFrame = setClippingFrame;
882
883 $$(Resource, addResourceProvider, shaderResourceProvider);
884}
885
890Class *_Renderer(void) {
891 static Class *clazz;
892 static Once once;
893
894 do_once(&once, {
895 clazz = _initialize(&(const ClassDef) {
896 .name = "Renderer",
897 .superclass = _Object(),
898 .instanceSize = sizeof(Renderer),
899 .interfaceSize = sizeof(RendererInterface),
900 .initialize = initialize,
901 });
902 });
903
904 return clazz;
905}
906
907#undef _Class
static View * init(View *self)
Definition Box.c:67
static void render(View *self, Renderer *renderer)
Definition Control.c:181
static String * name(const Font *self)
Definition Font.c:303
static Texture * texture(ImageAtlas *self, RenderDevice *device)
Definition ImageAtlas.c:289
static void drawTextureRegion(const Renderer *self, Texture *texture, const SDL_Rect *src, const SDL_FRect *dest, const SDL_Color *color)
Definition Renderer.c:497
static void drawView(Renderer *self, View *view)
Definition Renderer.c:525
static Data * shaderResourceProvider(const char *name)
ResourceProvider for loading shaders from xxd headers.
Definition Renderer.c:48
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...
Definition Renderer.c:151
static void drawLines(const Renderer *self, const SDL_Point *points, size_t count, const SDL_Color *color)
Definition Renderer.c:277
static void drawTexture(const Renderer *self, Texture *texture, const SDL_FRect *dest, const SDL_Color *color)
Definition Renderer.c:489
static void drawRoundedRectGradientFilled(const Renderer *self, const SDL_Rect *rect, int radius, int angle, const SDL_Color *from, const SDL_Color *to)
Definition Renderer.c:441
static void setClippingFrame(Renderer *self, const SDL_Rect *clippingFrame)
Definition Renderer.c:841
static void drawPolygon(const Renderer *self, const SDL_Point *points, size_t count, int width, const SDL_Color *color)
Definition Renderer.c:293
Class * _Renderer(void)
Definition Renderer.c:890
static void beginFrame(Renderer *self)
Definition Renderer.c:101
static void drawRoundedRect(const Renderer *self, const SDL_Rect *rect, int radius, int width, const SDL_Color *color)
Definition Renderer.c:407
static void drawRectFilled(const Renderer *self, const SDL_Rect *rect, const SDL_Color *color)
Definition Renderer.c:384
static void drawRoundedTextureRegion(const Renderer *self, Texture *texture, const SDL_Rect *src, const SDL_FRect *dest, int radius, const SDL_Color *color)
Definition Renderer.c:473
static void drawRect(const Renderer *self, const SDL_Rect *rect, const SDL_Color *color)
Definition Renderer.c:365
static Renderer * initWithDevice(Renderer *self, RenderDevice *device)
Definition Renderer.c:639
static void drawRoundedRectFilled(const Renderer *self, const SDL_Rect *rect, int radius, const SDL_Color *color)
Definition Renderer.c:424
static void drawPolygonFilled(const Renderer *self, const SDL_Point *points, size_t count, int angle, const SDL_Color *from, const SDL_Color *to)
Definition Renderer.c:310
static void pushDrawArrays(const Renderer *self, const MVC_Vertex *verts, size_t count, Texture *texture, const SDL_Color *color)
Definition Renderer.c:660
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.
Definition Renderer.c:202
static void dealloc(Object *self)
Definition Renderer.c:76
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.
Definition Renderer.c:170
static void drawLine(const Renderer *self, const SDL_Point *points, const SDL_Color *color)
Definition Renderer.c:266
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...
Definition Renderer.c:128
static void endFrame(Renderer *self)
Definition Renderer.c:555
static void drawBevel(const Renderer *self, const SDL_Rect *rect, int radius, int width, const SDL_Color *topLeft, const SDL_Color *bottomRight)
Definition Renderer.c:247
static void initialize(Class *clazz)
Definition Renderer.c:855
static void beginFrameWith(Renderer *self, CommandBuffer *commands, Framebuffer *framebuffer)
Definition Renderer.c:109
static void renderDeviceDidReset(Renderer *self)
Definition Renderer.c:705
static void drawRoundedTexture(const Renderer *self, Texture *texture, const SDL_FRect *dest, int radius, const SDL_Color *color)
Definition Renderer.c:465
static void renderDeviceWillReset(Renderer *self)
Definition Renderer.c:820
Renderer extends Object with ObjectivelyMVC's UI rendering layer.
static void resize(View *self, const SDL_Size *size)
Definition View.c:1708
static void draw(View *self, Renderer *renderer)
Definition View.c:778
static SDL_Rect clippingFrame(const View *self)
Definition View.c:606
static SDL_Rect bounds(const View *self)
Definition View.c:552
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.
Definition Window.c:29
Interleaved position + texcoord + color + rounded rect shape vertex for GPU upload.
Definition Renderer.h:51
SDL_Color color
Definition Renderer.h:54
vec2 position
Definition Renderer.h:52
Renderer extends Object with ObjectivelyMVC's UI rendering layer.
Definition Renderer.h:75
RenderDevice * device
The backing RenderDevice.
Definition Renderer.h:97
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.
Definition Renderer.c:660
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
Definition View.h:225
bool clipsSubviews
If true, subviews will be clipped to this View's frame.
Definition View.h:309
View * superview
The super View.
Definition View.h:454