Fortran · 14503 bytes Raw Blame History
1 module renderer_mod
2 use, intrinsic :: iso_c_binding
3 use gl_bindings
4 use glyph_mod
5 use font_mod
6 use atlas_mod
7 use shader_mod
8 implicit none
9 private
10
11 public :: renderer_t
12 public :: renderer_create, renderer_destroy
13 public :: renderer_begin, renderer_draw_char, renderer_draw_string, renderer_flush
14 public :: renderer_set_projection, renderer_load_fallback_font
15 public :: renderer_draw_rect, renderer_change_font_size
16
17 ! Vertex format: position(2) + texcoord(2) + color(4) = 8 floats per vertex
18 integer, parameter :: FLOATS_PER_VERTEX = 8
19 integer, parameter :: VERTICES_PER_QUAD = 6
20 integer, parameter :: FLOATS_PER_QUAD = FLOATS_PER_VERTEX * VERTICES_PER_QUAD
21 integer, parameter :: MAX_QUADS = 4096
22 integer, parameter :: MAX_VERTICES = MAX_QUADS * VERTICES_PER_QUAD
23 integer, parameter :: BUFFER_SIZE = MAX_VERTICES * FLOATS_PER_VERTEX
24
25 type :: renderer_t
26 integer :: vao = 0
27 integer :: vbo = 0
28 real(c_float), allocatable :: vertices(:)
29 integer :: vertex_count = 0
30 type(shader_t) :: shader
31 type(atlas_t) :: atlas
32 type(font_t) :: font
33 real(c_float) :: projection(4,4)
34 logical :: initialized = .false.
35 end type renderer_t
36
37 ! Embedded shader sources
38 character(len=*), parameter :: VERT_SHADER = &
39 "#version 330 core" // c_new_line // &
40 "layout(location = 0) in vec2 a_position;" // c_new_line // &
41 "layout(location = 1) in vec2 a_texcoord;" // c_new_line // &
42 "layout(location = 2) in vec4 a_color;" // c_new_line // &
43 "uniform mat4 u_projection;" // c_new_line // &
44 "out vec2 v_texcoord;" // c_new_line // &
45 "out vec4 v_color;" // c_new_line // &
46 "void main() {" // c_new_line // &
47 " gl_Position = u_projection * vec4(a_position, 0.0, 1.0);" // c_new_line // &
48 " v_texcoord = a_texcoord;" // c_new_line // &
49 " v_color = a_color;" // c_new_line // &
50 "}"
51
52 character(len=*), parameter :: FRAG_SHADER = &
53 "#version 330 core" // c_new_line // &
54 "in vec2 v_texcoord;" // c_new_line // &
55 "in vec4 v_color;" // c_new_line // &
56 "uniform sampler2D u_atlas;" // c_new_line // &
57 "out vec4 frag_color;" // c_new_line // &
58 "void main() {" // c_new_line // &
59 " float alpha = texture(u_atlas, v_texcoord).r;" // c_new_line // &
60 " frag_color = vec4(v_color.rgb, v_color.a * alpha);" // c_new_line // &
61 "}"
62
63 contains
64
65 ! Create renderer with font
66 function renderer_create(font_path, font_size) result(r)
67 character(len=*), intent(in) :: font_path
68 integer, intent(in) :: font_size
69 type(renderer_t) :: r
70 integer :: vao(1), vbo(1)
71 integer(c_size_t) :: stride, offset
72
73 ! Load font
74 r%font = font_load(font_path, font_size)
75 if (.not. r%font%loaded) then
76 print *, "Error: Failed to load font"
77 return
78 end if
79
80 ! Create atlas from font
81 r%atlas = atlas_create(r%font)
82 if (.not. r%atlas%initialized) then
83 print *, "Error: Failed to create atlas"
84 call font_destroy(r%font)
85 return
86 end if
87
88 ! Create shader
89 r%shader = shader_create(VERT_SHADER, FRAG_SHADER)
90 if (.not. r%shader%valid) then
91 print *, "Error: Failed to create shader"
92 call atlas_destroy(r%atlas)
93 call font_destroy(r%font)
94 return
95 end if
96
97 ! Create VAO and VBO
98 call glGenVertexArrays(1, vao)
99 call glGenBuffers(1, vbo)
100 r%vao = vao(1)
101 r%vbo = vbo(1)
102
103 call glBindVertexArray(r%vao)
104 call glBindBuffer(GL_ARRAY_BUFFER, r%vbo)
105
106 ! Allocate buffer (empty for now)
107 call glBufferData(GL_ARRAY_BUFFER, &
108 int(BUFFER_SIZE * c_sizeof(1.0_c_float), c_size_t), &
109 c_null_ptr, GL_DYNAMIC_DRAW)
110
111 ! Set up vertex attributes
112 stride = int(FLOATS_PER_VERTEX * c_sizeof(1.0_c_float), c_size_t)
113
114 ! Position: location 0, 2 floats, offset 0
115 offset = 0_c_size_t
116 call glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, int(stride, c_int), offset)
117 call glEnableVertexAttribArray(0)
118
119 ! Texcoord: location 1, 2 floats, offset 8 bytes
120 offset = int(2 * c_sizeof(1.0_c_float), c_size_t)
121 call glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, int(stride, c_int), offset)
122 call glEnableVertexAttribArray(1)
123
124 ! Color: location 2, 4 floats, offset 16 bytes
125 offset = int(4 * c_sizeof(1.0_c_float), c_size_t)
126 call glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, int(stride, c_int), offset)
127 call glEnableVertexAttribArray(2)
128
129 call glBindVertexArray(0)
130
131 ! Allocate vertex buffer
132 allocate(r%vertices(BUFFER_SIZE))
133 r%vertices = 0.0_c_float
134 r%vertex_count = 0
135
136 ! Initialize projection to identity
137 r%projection = 0.0_c_float
138 r%projection(1,1) = 1.0_c_float
139 r%projection(2,2) = 1.0_c_float
140 r%projection(3,3) = 1.0_c_float
141 r%projection(4,4) = 1.0_c_float
142
143 r%initialized = .true.
144
145 end function renderer_create
146
147 ! Destroy renderer
148 subroutine renderer_destroy(r)
149 type(renderer_t), intent(inout) :: r
150 integer :: arr(1)
151
152 if (r%vbo > 0) then
153 arr(1) = r%vbo
154 call glDeleteBuffers(1, arr)
155 r%vbo = 0
156 end if
157
158 if (r%vao > 0) then
159 arr(1) = r%vao
160 call glDeleteVertexArrays(1, arr)
161 r%vao = 0
162 end if
163
164 call shader_destroy(r%shader)
165 call atlas_destroy(r%atlas)
166 call font_destroy(r%font)
167
168 if (allocated(r%vertices)) deallocate(r%vertices)
169 r%initialized = .false.
170 end subroutine renderer_destroy
171
172 ! Begin a new frame (reset vertex buffer)
173 subroutine renderer_begin(r)
174 type(renderer_t), intent(inout) :: r
175 r%vertex_count = 0
176 end subroutine renderer_begin
177
178 ! Set orthographic projection matrix
179 subroutine renderer_set_projection(r, width, height)
180 type(renderer_t), intent(inout) :: r
181 integer, intent(in) :: width, height
182
183 ! Orthographic projection: (0,0) top-left, (width, height) bottom-right
184 r%projection = 0.0_c_float
185 r%projection(1,1) = 2.0_c_float / real(width, c_float)
186 r%projection(2,2) = -2.0_c_float / real(height, c_float) ! Flip Y
187 r%projection(3,3) = -1.0_c_float
188 r%projection(4,4) = 1.0_c_float
189 r%projection(1,4) = -1.0_c_float
190 r%projection(2,4) = 1.0_c_float
191 end subroutine renderer_set_projection
192
193 ! Draw a single character at position (x, y)
194 subroutine renderer_draw_char(r, x, y, codepoint, red, green, blue, alpha)
195 type(renderer_t), intent(inout) :: r
196 real, intent(in) :: x, y
197 integer, intent(in) :: codepoint
198 real, intent(in) :: red, green, blue, alpha
199 type(glyph_t) :: g
200 real(c_float) :: x0, y0, x1, y1
201 real(c_float) :: u0, v0, u1, v1
202 integer :: base
203
204 if (.not. r%initialized) return
205
206 g = atlas_get_glyph(r%atlas, codepoint)
207 if (.not. g%valid) return
208
209 ! Check if buffer is full
210 if (r%vertex_count + VERTICES_PER_QUAD > MAX_VERTICES) then
211 call renderer_flush(r)
212 end if
213
214 ! Calculate quad positions
215 ! x, y is the cursor position (baseline)
216 x0 = real(x, c_float) + real(g%bearing_x, c_float)
217 y0 = real(y, c_float) - real(g%bearing_y, c_float)
218 x1 = x0 + real(g%width, c_float)
219 y1 = y0 + real(g%height, c_float)
220
221 u0 = g%u0
222 v0 = g%v0
223 u1 = g%u1
224 v1 = g%v1
225
226 ! Add 6 vertices (2 triangles) for the quad
227 base = r%vertex_count * FLOATS_PER_VERTEX + 1
228
229 ! Triangle 1: top-left, bottom-left, bottom-right
230 ! Vertex 1: top-left
231 r%vertices(base:base+7) = [x0, y0, u0, v0, &
232 real(red,c_float), real(green,c_float), &
233 real(blue,c_float), real(alpha,c_float)]
234 base = base + 8
235
236 ! Vertex 2: bottom-left
237 r%vertices(base:base+7) = [x0, y1, u0, v1, &
238 real(red,c_float), real(green,c_float), &
239 real(blue,c_float), real(alpha,c_float)]
240 base = base + 8
241
242 ! Vertex 3: bottom-right
243 r%vertices(base:base+7) = [x1, y1, u1, v1, &
244 real(red,c_float), real(green,c_float), &
245 real(blue,c_float), real(alpha,c_float)]
246 base = base + 8
247
248 ! Triangle 2: top-left, bottom-right, top-right
249 ! Vertex 4: top-left
250 r%vertices(base:base+7) = [x0, y0, u0, v0, &
251 real(red,c_float), real(green,c_float), &
252 real(blue,c_float), real(alpha,c_float)]
253 base = base + 8
254
255 ! Vertex 5: bottom-right
256 r%vertices(base:base+7) = [x1, y1, u1, v1, &
257 real(red,c_float), real(green,c_float), &
258 real(blue,c_float), real(alpha,c_float)]
259 base = base + 8
260
261 ! Vertex 6: top-right
262 r%vertices(base:base+7) = [x1, y0, u1, v0, &
263 real(red,c_float), real(green,c_float), &
264 real(blue,c_float), real(alpha,c_float)]
265
266 r%vertex_count = r%vertex_count + VERTICES_PER_QUAD
267
268 end subroutine renderer_draw_char
269
270 ! Draw a string at position (x, y)
271 subroutine renderer_draw_string(r, x, y, text, red, green, blue, alpha)
272 type(renderer_t), intent(inout) :: r
273 real, intent(in) :: x, y
274 character(len=*), intent(in) :: text
275 real, intent(in) :: red, green, blue, alpha
276 integer :: i, cp
277 real :: cursor_x
278 type(glyph_t) :: g
279
280 cursor_x = x
281 do i = 1, len_trim(text)
282 cp = ichar(text(i:i))
283 call renderer_draw_char(r, cursor_x, y, cp, red, green, blue, alpha)
284
285 ! Advance cursor
286 g = atlas_get_glyph(r%atlas, cp)
287 if (g%valid) then
288 cursor_x = cursor_x + real(g%advance)
289 end if
290 end do
291 end subroutine renderer_draw_string
292
293 ! Flush the vertex buffer to GPU and draw
294 subroutine renderer_flush(r)
295 type(renderer_t), intent(inout) :: r
296 integer(c_size_t) :: data_size
297
298 if (r%vertex_count == 0) return
299
300 ! Enable blending for text
301 call glEnable(GL_BLEND)
302 call glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
303
304 ! Bind shader and set uniforms
305 call shader_use(r%shader)
306 call shader_set_projection(r%shader, r%projection)
307
308 ! Bind texture
309 call glActiveTexture(GL_TEXTURE0)
310 call glBindTexture(GL_TEXTURE_2D, r%atlas%texture_id)
311 call glUniform1i(r%shader%atlas_loc, 0)
312
313 ! Upload vertex data
314 call glBindVertexArray(r%vao)
315 call glBindBuffer(GL_ARRAY_BUFFER, r%vbo)
316
317 data_size = int(r%vertex_count * FLOATS_PER_VERTEX * c_sizeof(1.0_c_float), c_size_t)
318 call glBufferSubData_floats(GL_ARRAY_BUFFER, 0_c_size_t, data_size, r%vertices)
319
320 ! Draw
321 call glDrawArrays(GL_TRIANGLES, 0, r%vertex_count)
322
323 ! Unbind
324 call glBindVertexArray(0)
325 call glBindTexture(GL_TEXTURE_2D, 0)
326
327 ! Reset vertex count
328 r%vertex_count = 0
329
330 end subroutine renderer_flush
331
332 ! Load a fallback font for missing glyphs
333 subroutine renderer_load_fallback_font(r, font_path)
334 type(renderer_t), intent(inout) :: r
335 character(len=*), intent(in) :: font_path
336
337 if (.not. r%initialized) return
338 call font_load_fallback(r%font, font_path)
339 end subroutine renderer_load_fallback_font
340
341 ! Draw a solid rectangle at position (x, y) with size (w, h)
342 subroutine renderer_draw_rect(r, x, y, w, h, red, green, blue, alpha)
343 type(renderer_t), intent(inout) :: r
344 real, intent(in) :: x, y, w, h
345 real, intent(in) :: red, green, blue, alpha
346 real(c_float) :: x0, y0, x1, y1
347 real(c_float) :: u0, v0, u1, v1
348 integer :: base
349
350 ! Check for overflow
351 if (r%vertex_count + VERTICES_PER_QUAD > MAX_VERTICES) then
352 call renderer_flush(r)
353 end if
354
355 ! Rectangle corners
356 x0 = real(x, c_float)
357 y0 = real(y, c_float)
358 x1 = x0 + real(w, c_float)
359 y1 = y0 + real(h, c_float)
360
361 ! Sample from center of white pixel at atlas origin (0.5, 0.5) in pixel coords
362 ! Atlas is 1024x1024, so UV center = 0.5/1024 ≈ 0.000488
363 ! Use same UV for all vertices to get solid color (no interpolation)
364 u0 = 0.0005_c_float
365 v0 = 0.0005_c_float
366 u1 = 0.0005_c_float
367 v1 = 0.0005_c_float
368
369 ! Build 6 vertices for 2 triangles
370 base = r%vertex_count * FLOATS_PER_VERTEX + 1
371
372 ! Triangle 1: bottom-left, bottom-right, top-left
373 r%vertices(base:base+7) = [x0, y1, u0, v1, &
374 real(red,c_float), real(green,c_float), &
375 real(blue,c_float), real(alpha,c_float)]
376 base = base + FLOATS_PER_VERTEX
377
378 r%vertices(base:base+7) = [x1, y1, u1, v1, &
379 real(red,c_float), real(green,c_float), &
380 real(blue,c_float), real(alpha,c_float)]
381 base = base + FLOATS_PER_VERTEX
382
383 r%vertices(base:base+7) = [x0, y0, u0, v0, &
384 real(red,c_float), real(green,c_float), &
385 real(blue,c_float), real(alpha,c_float)]
386 base = base + FLOATS_PER_VERTEX
387
388 ! Triangle 2: bottom-right, top-right, top-left
389 r%vertices(base:base+7) = [x1, y1, u1, v1, &
390 real(red,c_float), real(green,c_float), &
391 real(blue,c_float), real(alpha,c_float)]
392 base = base + FLOATS_PER_VERTEX
393
394 r%vertices(base:base+7) = [x1, y0, u1, v0, &
395 real(red,c_float), real(green,c_float), &
396 real(blue,c_float), real(alpha,c_float)]
397 base = base + FLOATS_PER_VERTEX
398
399 r%vertices(base:base+7) = [x0, y0, u0, v0, &
400 real(red,c_float), real(green,c_float), &
401 real(blue,c_float), real(alpha,c_float)]
402
403 r%vertex_count = r%vertex_count + VERTICES_PER_QUAD
404
405 end subroutine renderer_draw_rect
406
407 ! Change font size at runtime (destroys old font/atlas, creates new ones)
408 subroutine renderer_change_font_size(r, font_path, new_size)
409 type(renderer_t), intent(inout) :: r
410 character(len=*), intent(in) :: font_path
411 integer, intent(in) :: new_size
412
413 if (.not. r%initialized) return
414
415 ! Destroy old atlas and font
416 call atlas_destroy(r%atlas)
417 call font_destroy(r%font)
418
419 ! Load font with new size
420 r%font = font_load(font_path, new_size)
421 if (.not. r%font%loaded) then
422 print *, "Error: Failed to reload font at size", new_size
423 r%initialized = .false.
424 return
425 end if
426
427 ! Recreate atlas with new font
428 r%atlas = atlas_create(r%font)
429 if (.not. r%atlas%initialized) then
430 print *, "Error: Failed to recreate atlas"
431 call font_destroy(r%font)
432 r%initialized = .false.
433 return
434 end if
435
436 end subroutine renderer_change_font_size
437
438 end module renderer_mod
439