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 <stdlib.h>
26#include <string.h>
27
28#include <Objectively/Vector.h>
29#include <ObjectivelyGPU/CopyPass.h>
30#include <ObjectivelyGPU/Mathlib.h>
31#include <ObjectivelyGPU/RenderPass.h>
32
33#include "Renderer.h"
34#include "View.h"
35#include "Window.h"
36
37#include "../Assets/Renderer.frag.dxil.h"
38#include "../Assets/Renderer.frag.metal.h"
39#include "../Assets/Renderer.frag.spv.h"
40#include "../Assets/Renderer.vert.dxil.h"
41#include "../Assets/Renderer.vert.metal.h"
42#include "../Assets/Renderer.vert.spv.h"
43
47static Data *shaderResourceProvider(const char *name) {
48
49 Data *data = NULL;
50
51 if (!strcmp("Renderer.frag.dxil", name)) {
52 data = $(alloc(Data), initWithConstMemory, Renderer_frag_dxil, Renderer_frag_dxil_len);
53 } else if (!strcmp("Renderer.frag.metal", name)) {
54 data = $(alloc(Data), initWithConstMemory, Renderer_frag_metal, Renderer_frag_metal_len);
55 } else if (!strcmp("Renderer.frag.spv", name)) {
56 data = $(alloc(Data), initWithConstMemory, Renderer_frag_spv, Renderer_frag_spv_len);
57 } else if (!strcmp("Renderer.vert.dxil", name)) {
58 data = $(alloc(Data), initWithConstMemory, Renderer_vert_dxil, Renderer_vert_dxil_len);
59 } else if (!strcmp("Renderer.vert.metal", name)) {
60 data = $(alloc(Data), initWithConstMemory, Renderer_vert_metal, Renderer_vert_metal_len);
61 } else if (!strcmp("Renderer.vert.spv", name)) {
62 data = $(alloc(Data), initWithConstMemory, Renderer_vert_spv, Renderer_vert_spv_len);
63 }
64
65 return data;
66}
67
68#define _Class _Renderer
69
70#pragma mark - Object
71
75static void dealloc(Object *self) {
76
77 Renderer *this = (Renderer *) self;
78
79 this->commands = NULL;
80
81 release(this->white);
82 release(this->sampler);
83 release(this->vertexBuffer);
84 release(this->transferBuffer);
85 release(this->pipeline);
86
87 release(this->vertices);
88 release(this->drawArrays);
89 release(this->device);
90
91 super(Object, self, dealloc);
92}
93
94#pragma mark - Renderer
95
100static void beginFrame(Renderer *self) {
101 $(self, beginFrameWith, self->device->commands, self->device->framebuffer);
102}
103
108static void beginFrameWith(Renderer *self, CommandBuffer *commands, Framebuffer *framebuffer) {
109
110 assert(commands);
111 assert(framebuffer);
112
113 self->commands = commands;
114 self->framebuffer = framebuffer;
115
116 $(self->vertices, removeAll);
117 $(self->drawArrays, removeAll);
118
119 self->scissor = MakeRect(0, 0, framebuffer->size.w, framebuffer->size.h);
120}
121
126static void drawLine(const Renderer *self, const SDL_Point *points, const SDL_Color *color) {
127
128 assert(points);
129
130 $(self, drawLines, points, 2, color);
131}
132
137static void drawLines(const Renderer *self, const SDL_Point *points, size_t count, const SDL_Color *color) {
138
139 assert(points);
140 assert(color);
141
142 if (count < 2) {
143 return;
144 }
145
146 const size_t segCount = count - 1;
147 MVC_Vertex *verts = malloc(segCount * 6 * sizeof(MVC_Vertex));
148 assert(verts);
149
150 for (size_t i = 0; i < segCount; i++) {
151 const float ax = (float) points[i].x, ay = (float) points[i].y;
152 const float bx = (float) points[i+1].x, by = (float) points[i+1].y;
153
154 const float dx = bx - ax, dy = by - ay;
155 const float len = sqrtf(dx * dx + dy * dy);
156
157 float nx = 0.0f, ny = 0.0f;
158 if (len > 0.001f) {
159 nx = (-dy / len) * 0.5f;
160 ny = ( dx / len) * 0.5f;
161 }
162
163 MVC_Vertex *v = &verts[i * 6];
164 v[0] = (MVC_Vertex) { { { ax - nx, ay - ny } }, { { 0.0f, 0.0f } }, { 0 } };
165 v[1] = (MVC_Vertex) { { { ax + nx, ay + ny } }, { { 0.0f, 0.0f } }, { 0 } };
166 v[2] = (MVC_Vertex) { { { bx - nx, by - ny } }, { { 0.0f, 0.0f } }, { 0 } };
167 v[3] = (MVC_Vertex) { { { ax + nx, ay + ny } }, { { 0.0f, 0.0f } }, { 0 } };
168 v[4] = (MVC_Vertex) { { { bx + nx, by + ny } }, { { 0.0f, 0.0f } }, { 0 } };
169 v[5] = (MVC_Vertex) { { { bx - nx, by - ny } }, { { 0.0f, 0.0f } }, { 0 } };
170 }
171
172 $(self, pushDrawArrays, verts, segCount * 6, NULL, color);
173
174 free(verts);
175}
176
181static void drawRect(const Renderer *self, const SDL_Rect *rect, const SDL_Color *color) {
182
183 assert(rect);
184
185 const SDL_Point points[5] = {
186 { rect->x, rect->y },
187 { rect->x + rect->w, rect->y },
188 { rect->x + rect->w, rect->y + rect->h },
189 { rect->x, rect->y + rect->h },
190 { rect->x, rect->y },
191 };
192
193 $(self, drawLines, points, 5, color);
194}
195
200static void drawRectFilled(const Renderer *self, const SDL_Rect *rect, const SDL_Color *color) {
201
202 assert(rect);
203
204 const float x1 = (float) rect->x, y1 = (float) rect->y;
205 const float x2 = (float) rect->x + rect->w, y2 = (float) rect->y + rect->h;
206
207 const MVC_Vertex verts[6] = {
208 { { { x1, y1 } }, { { 0.0f, 0.0f } }, { 0 } },
209 { { { x2, y1 } }, { { 0.0f, 0.0f } }, { 0 } },
210 { { { x1, y2 } }, { { 0.0f, 0.0f } }, { 0 } },
211 { { { x2, y1 } }, { { 0.0f, 0.0f } }, { 0 } },
212 { { { x2, y2 } }, { { 0.0f, 0.0f } }, { 0 } },
213 { { { x1, y2 } }, { { 0.0f, 0.0f } }, { 0 } },
214 };
215
216 $(self, pushDrawArrays, verts, 6, NULL, color);
217}
218
223static void drawTexture(const Renderer *self, Texture *texture, const SDL_Rect *rect, const SDL_Color *color) {
224
225 assert(rect);
226
227 const float x1 = (float) rect->x, y1 = (float) rect->y;
228 const float x2 = (float) rect->x + rect->w, y2 = (float) rect->y + rect->h;
229
230 const MVC_Vertex verts[6] = {
231 { { { x1, y1 } }, { { 0.0f, 0.0f } }, { 0 } },
232 { { { x2, y1 } }, { { 1.0f, 0.0f } }, { 0 } },
233 { { { x1, y2 } }, { { 0.0f, 1.0f } }, { 0 } },
234 { { { x2, y1 } }, { { 1.0f, 0.0f } }, { 0 } },
235 { { { x2, y2 } }, { { 1.0f, 1.0f } }, { 0 } },
236 { { { x1, y2 } }, { { 0.0f, 1.0f } }, { 0 } },
237 };
238
239 $(self, pushDrawArrays, verts, 6, texture, color);
240}
241
246static void drawView(Renderer *self, View *view) {
247
248 assert(view);
249
250 const SDL_Rect clippingFrame = $(view, clippingFrame);
251 if (clippingFrame.w && clippingFrame.h) {
253 $(view, render, self);
254 }
255}
256
261static void endFrame(Renderer *self) {
262
263 Framebuffer *framebuffer = self->framebuffer;
264 assert(framebuffer);
265
266 const SDL_GPUColorTargetInfo colorTarget = $(framebuffer, colorTargetInfo, 0, SDL_GPU_LOADOP_LOAD, SDL_GPU_STOREOP_STORE);
267
268 const size_t vertexCount = self->vertices->count;
269
270 CopyPass *copyPass = $(self->commands, beginCopyPass);
271
272 if (vertexCount > 0) {
273 const Uint32 vertexSize = (Uint32) (vertexCount * sizeof(MVC_Vertex));
274
275 if (vertexCount > self->vertexBufferCapacity) {
276 release(self->vertexBuffer);
277 self->vertexBuffer = $(self->device, createBuffer, &(SDL_GPUBufferCreateInfo) {
278 .usage = SDL_GPU_BUFFERUSAGE_VERTEX,
279 .size = vertexSize
280 });
281
282 release(self->transferBuffer);
283 self->transferBuffer = $(self->device, createTransferBuffer, &(SDL_GPUTransferBufferCreateInfo) {
284 .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
285 .size = vertexSize,
286 });
287
288 self->vertexBufferCapacity = (Uint32) vertexCount;
289 }
290
291 $(self->transferBuffer, write, self->vertices->elements, vertexSize, true);
292
293 $(copyPass, uploadBuffer,
294 &(SDL_GPUTransferBufferLocation) { .transfer_buffer = self->transferBuffer->buffer },
295 &(SDL_GPUBufferRegion) { .buffer = self->vertexBuffer->buffer, .size = vertexSize },
296 true);
297 }
298
299 release(copyPass);
300
301 RenderPass *renderPass = $(self->commands, beginRenderPass, &colorTarget, 1, NULL);
302
303 $(renderPass, setViewport, &(SDL_GPUViewport){
304 .x = 0.0f, .y = 0.0f,
305 .w = (float) framebuffer->size.w, .h = (float) framebuffer->size.h,
306 .min_depth = 0.0f, .max_depth = 1.0f,
307 });
308
309 int winW, winH;
310 SDL_GetWindowSize(self->device->window, &winW, &winH);
311 const mat4 projection = mat4_ortho(0.f, (float) winW, (float) winH, 0.f, -1.f, 1.f);
312 $(self->commands, pushVertexUniformData, 0, projection.f, sizeof(projection));
313
314 $(renderPass, bindPipeline, self->pipeline);
315 $(renderPass, bindVertexBuffers, 0, &(SDL_GPUBufferBinding) { .buffer = self->vertexBuffer->buffer }, 1);
316
317 for (size_t i = 0; i < self->drawArrays->count; i++) {
318 const MVC_DrawArrays *draw = VectorElement(self->drawArrays, MVC_DrawArrays, i);
319
320 $(renderPass, setScissor, &draw->scissor);
321
322 $(renderPass, bindFragmentSamplers, 0, &(SDL_GPUTextureSamplerBinding) {
323 .texture = draw->texture->texture, .sampler = self->sampler->sampler,
324 }, 1);
325
326 $(renderPass, drawPrimitives, draw->vertexCount, 1, draw->firstVertex, 0);
327 }
328
329 release(renderPass);
330
331 self->commands = NULL;
332 self->framebuffer = NULL;
333}
334
339static Renderer *initWithDevice(Renderer *self, RenderDevice *device) {
340
341 self = (Renderer *) super(Object, self, init);
342 if (self) {
343 self->device = retain(device);
344 assert(self->device);
345
346 self->vertices = $(alloc(Vector), initWithSize, sizeof(MVC_Vertex));
347 assert(self->vertices);
348
349 self->drawArrays = $(alloc(Vector), initWithSize, sizeof(MVC_DrawArrays));
350 assert(self->drawArrays);
351 }
352
353 return self;
354}
355
360static void pushDrawArrays(const Renderer *self, const MVC_Vertex *verts, size_t count, Texture *texture, const SDL_Color *color) {
361
362 assert(verts);
363 assert(color);
364
365 const MVC_DrawArrays draw = {
366 .firstVertex = (Uint32) self->vertices->count,
367 .vertexCount = (Uint32) count,
368 .texture = texture ? texture : self->white,
369 .scissor = self->scissor,
370 };
371
372 for (size_t i = 0; i < count; i++) {
373 MVC_Vertex v = verts[i];
374 v.color = *color;
375 $(self->vertices, add, &v);
376 }
377
378 $(self->drawArrays, add, (MVC_DrawArrays *) &draw);
379}
380
385static void renderDeviceDidReset(Renderer *self) {
386
387 Shader *vertexShader = $(self->device, loadShader, "Renderer.vert", &(SDL_GPUShaderCreateInfo) {
388 .stage = SDL_GPU_SHADERSTAGE_VERTEX,
389 .num_uniform_buffers = 1,
390 });
391
392 Shader *fragmentShader = $(self->device, loadShader, "Renderer.frag", &(SDL_GPUShaderCreateInfo) {
393 .stage = SDL_GPU_SHADERSTAGE_FRAGMENT,
394 .num_samplers = 1,
395 });
396
397 const SDL_GPUVertexBufferDescription vertexBufferInfo = {
398 .slot = 0,
399 .pitch = sizeof(MVC_Vertex),
400 .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX,
401 .instance_step_rate = 0,
402 };
403
404 const SDL_GPUColorTargetDescription colorTarget = {
405 .format = $(self->device, getSwapchainTextureFormat),
406 .blend_state = {
407 .enable_blend = true,
408 .color_blend_op = SDL_GPU_BLENDOP_ADD,
409 .alpha_blend_op = SDL_GPU_BLENDOP_ADD,
410 .src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
411 .dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
412 .src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE,
413 .dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
414 },
415 };
416
417 const SDL_GPUGraphicsPipelineCreateInfo pipelineInfo = {
418 .vertex_shader = vertexShader->shader,
419 .fragment_shader = fragmentShader->shader,
420 .vertex_input_state = {
421 .vertex_buffer_descriptions = &vertexBufferInfo,
422 .num_vertex_buffers = 1,
423 .vertex_attributes = (SDL_GPUVertexAttribute[]) {
424 {
425 .location = 0,
426 .buffer_slot = 0,
427 .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
428 .offset = offsetof(MVC_Vertex, position)
429 },
430 {
431 .location = 1,
432 .buffer_slot = 0,
433 .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
434 .offset = offsetof(MVC_Vertex, uv)
435 },
436 {
437 .location = 2,
438 .buffer_slot = 0,
439 .format = SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4_NORM,
440 .offset = offsetof(MVC_Vertex, color)
441 },
442 },
443 .num_vertex_attributes = 3,
444 },
445 .primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
446 .rasterizer_state = {
447 .fill_mode = SDL_GPU_FILLMODE_FILL,
448 .cull_mode = SDL_GPU_CULLMODE_NONE,
449 .front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE,
450 },
451 .target_info = {
452 .color_target_descriptions = &colorTarget,
453 .num_color_targets = 1,
454 },
455 };
456
457 self->pipeline = $(self->device, createGraphicsPipeline, &pipelineInfo);
458
459 release(vertexShader);
460 release(fragmentShader);
461
462 self->sampler = $(self->device, createSampler, &(const SDL_GPUSamplerCreateInfo) {
463 .min_filter = SDL_GPU_FILTER_LINEAR,
464 .mag_filter = SDL_GPU_FILTER_LINEAR,
465 .mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR,
466 .address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
467 .address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
468 .address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
469 });
470
471 const Uint8 white[4] = { 255, 255, 255, 255 };
472
473 self->white = $(self->device, createTexture, &(const SDL_GPUTextureCreateInfo) {
474 .type = SDL_GPU_TEXTURETYPE_2D,
475 .format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
476 .usage = SDL_GPU_TEXTUREUSAGE_SAMPLER,
477 .width = 1,
478 .height = 1,
479 .layer_count_or_depth = 1,
480 .num_levels = 1,
481 }, white);
482}
483
488static void renderDeviceWillReset(Renderer *self) {
489
490 self->commands = NULL;
491
492 self->white = release(self->white);
493 self->sampler = release(self->sampler);
494
495 self->vertexBuffer = release(self->vertexBuffer);
496 self->transferBuffer = release(self->transferBuffer);
497 self->vertexBufferCapacity = 0;
498
499 self->pipeline = release(self->pipeline);
500
501 $(self->vertices, removeAll);
502 $(self->drawArrays, removeAll);
503}
504
509static void setClippingFrame(Renderer *self, const SDL_Rect *clippingFrame) {
510
511 if (clippingFrame) {
512 self->scissor = MVC_TransformToWindow(self->device->window, clippingFrame);
513 } else {
514 self->scissor = MakeRect(0, 0, self->framebuffer->size.w, self->framebuffer->size.h);
515 }
516}
517
518#pragma mark - Class lifecycle
519
523static void initialize(Class *clazz) {
524
525 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
526
527 ((RendererInterface *) clazz->interface)->beginFrame = beginFrame;
528 ((RendererInterface *) clazz->interface)->beginFrameWith = beginFrameWith;
529 ((RendererInterface *) clazz->interface)->drawLine = drawLine;
530 ((RendererInterface *) clazz->interface)->drawLines = drawLines;
531 ((RendererInterface *) clazz->interface)->drawRect = drawRect;
532 ((RendererInterface *) clazz->interface)->drawRectFilled = drawRectFilled;
533 ((RendererInterface *) clazz->interface)->drawTexture = drawTexture;
534 ((RendererInterface *) clazz->interface)->drawView = drawView;
535 ((RendererInterface *) clazz->interface)->endFrame = endFrame;
536 ((RendererInterface *) clazz->interface)->initWithDevice = initWithDevice;
537 ((RendererInterface *) clazz->interface)->pushDrawArrays = pushDrawArrays;
538 ((RendererInterface *) clazz->interface)->renderDeviceDidReset = renderDeviceDidReset;
539 ((RendererInterface *) clazz->interface)->renderDeviceWillReset = renderDeviceWillReset;
540 ((RendererInterface *) clazz->interface)->setClippingFrame = setClippingFrame;
541
542 $$(Resource, addResourceProvider, shaderResourceProvider);
543}
544
549Class *_Renderer(void) {
550 static Class *clazz;
551 static Once once;
552
553 do_once(&once, {
554 clazz = _initialize(&(const ClassDef) {
555 .name = "Renderer",
556 .superclass = _Object(),
557 .instanceSize = sizeof(Renderer),
558 .interfaceOffset = offsetof(Renderer, interface),
559 .interfaceSize = sizeof(RendererInterface),
561 });
562 });
563
564 return clazz;
565}
566
567#undef _Class
static View * init(View *self)
Definition Box.c:67
static void render(View *self, Renderer *renderer)
Definition Control.c:179
static void drawView(Renderer *self, View *view)
Definition Renderer.c:246
static Data * shaderResourceProvider(const char *name)
ResourceProvider for loading shaders from xxd headers.
Definition Renderer.c:47
static void drawLines(const Renderer *self, const SDL_Point *points, size_t count, const SDL_Color *color)
Definition Renderer.c:137
static void setClippingFrame(Renderer *self, const SDL_Rect *clippingFrame)
Definition Renderer.c:509
Class * _Renderer(void)
Definition Renderer.c:549
static void beginFrame(Renderer *self)
Definition Renderer.c:100
static void drawRectFilled(const Renderer *self, const SDL_Rect *rect, const SDL_Color *color)
Definition Renderer.c:200
static void drawRect(const Renderer *self, const SDL_Rect *rect, const SDL_Color *color)
Definition Renderer.c:181
static Renderer * initWithDevice(Renderer *self, RenderDevice *device)
Definition Renderer.c:339
static void pushDrawArrays(const Renderer *self, const MVC_Vertex *verts, size_t count, Texture *texture, const SDL_Color *color)
Definition Renderer.c:360
static void drawTexture(const Renderer *self, Texture *texture, const SDL_Rect *rect, const SDL_Color *color)
Definition Renderer.c:223
static void dealloc(Object *self)
Definition Renderer.c:75
static void drawLine(const Renderer *self, const SDL_Point *points, const SDL_Color *color)
Definition Renderer.c:126
static void endFrame(Renderer *self)
Definition Renderer.c:261
static void initialize(Class *clazz)
Definition Renderer.c:523
static void beginFrameWith(Renderer *self, CommandBuffer *commands, Framebuffer *framebuffer)
Definition Renderer.c:108
static void renderDeviceDidReset(Renderer *self)
Definition Renderer.c:385
static void renderDeviceWillReset(Renderer *self)
Definition Renderer.c:488
Renderer extends Object with ObjectivelyMVC's UI rendering layer.
static void draw(View *self, Renderer *renderer)
Definition View.c:709
static SDL_Rect clippingFrame(const View *self)
Definition View.c:546
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 vertex for GPU upload.
Definition Renderer.h:48
SDL_Color color
Definition Renderer.h:51
Renderer extends Object with ObjectivelyMVC's UI rendering layer.
Definition Renderer.h:70
RenderDevice * device
The backing RenderDevice.
Definition Renderer.h:92
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:360
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
Definition View.h:134