Fortran · 10610 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
15
16 ! Vertex format: position(2) + texcoord(2) + color(4) = 8 floats per vertex
17 integer, parameter :: FLOATS_PER_VERTEX = 8
18 integer, parameter :: VERTICES_PER_QUAD = 6
19 integer, parameter :: FLOATS_PER_QUAD = FLOATS_PER_VERTEX * VERTICES_PER_QUAD
20 integer, parameter :: MAX_QUADS = 4096
21 integer, parameter :: MAX_VERTICES = MAX_QUADS * VERTICES_PER_QUAD
22 integer, parameter :: BUFFER_SIZE = MAX_VERTICES * FLOATS_PER_VERTEX
23
24 type :: renderer_t
25 integer :: vao = 0
26 integer :: vbo = 0
27 real(c_float), allocatable :: vertices(:)
28 integer :: vertex_count = 0
29 type(shader_t) :: shader
30 type(atlas_t) :: atlas
31 type(font_t) :: font
32 real(c_float) :: projection(4,4)
33 logical :: initialized = .false.
34 end type renderer_t
35
36 ! Embedded shader sources
37 character(len=*), parameter :: VERT_SHADER = &
38 "#version 330 core" // c_new_line // &
39 "layout(location = 0) in vec2 a_position;" // c_new_line // &
40 "layout(location = 1) in vec2 a_texcoord;" // c_new_line // &
41 "layout(location = 2) in vec4 a_color;" // c_new_line // &
42 "uniform mat4 u_projection;" // c_new_line // &
43 "out vec2 v_texcoord;" // c_new_line // &
44 "out vec4 v_color;" // c_new_line // &
45 "void main() {" // c_new_line // &
46 " gl_Position = u_projection * vec4(a_position, 0.0, 1.0);" // c_new_line // &
47 " v_texcoord = a_texcoord;" // c_new_line // &
48 " v_color = a_color;" // c_new_line // &
49 "}"
50
51 character(len=*), parameter :: FRAG_SHADER = &
52 "#version 330 core" // c_new_line // &
53 "in vec2 v_texcoord;" // c_new_line // &
54 "in vec4 v_color;" // c_new_line // &
55 "uniform sampler2D u_atlas;" // c_new_line // &
56 "out vec4 frag_color;" // c_new_line // &
57 "void main() {" // c_new_line // &
58 " float alpha = texture(u_atlas, v_texcoord).r;" // c_new_line // &
59 " frag_color = vec4(v_color.rgb, v_color.a * alpha);" // c_new_line // &
60 "}"
61
62 contains
63
64 ! Create renderer with font
65 function renderer_create(font_path, font_size) result(r)
66 character(len=*), intent(in) :: font_path
67 integer, intent(in) :: font_size
68 type(renderer_t) :: r
69 integer :: vao(1), vbo(1)
70 integer(c_size_t) :: stride, offset
71
72 ! Load font
73 r%font = font_load(font_path, font_size)
74 if (.not. r%font%loaded) then
75 print *, "Error: Failed to load font"
76 return
77 end if
78
79 ! Create atlas from font
80 r%atlas = atlas_create(r%font)
81 if (.not. r%atlas%initialized) then
82 print *, "Error: Failed to create atlas"
83 call font_destroy(r%font)
84 return
85 end if
86
87 ! Create shader
88 r%shader = shader_create(VERT_SHADER, FRAG_SHADER)
89 if (.not. r%shader%valid) then
90 print *, "Error: Failed to create shader"
91 call atlas_destroy(r%atlas)
92 call font_destroy(r%font)
93 return
94 end if
95
96 ! Create VAO and VBO
97 call glGenVertexArrays(1, vao)
98 call glGenBuffers(1, vbo)
99 r%vao = vao(1)
100 r%vbo = vbo(1)
101
102 call glBindVertexArray(r%vao)
103 call glBindBuffer(GL_ARRAY_BUFFER, r%vbo)
104
105 ! Allocate buffer (empty for now)
106 call glBufferData(GL_ARRAY_BUFFER, &
107 int(BUFFER_SIZE * c_sizeof(1.0_c_float), c_size_t), &
108 c_null_ptr, GL_DYNAMIC_DRAW)
109
110 ! Set up vertex attributes
111 stride = int(FLOATS_PER_VERTEX * c_sizeof(1.0_c_float), c_size_t)
112
113 ! Position: location 0, 2 floats, offset 0
114 offset = 0_c_size_t
115 call glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, int(stride, c_int), offset)
116 call glEnableVertexAttribArray(0)
117
118 ! Texcoord: location 1, 2 floats, offset 8 bytes
119 offset = int(2 * c_sizeof(1.0_c_float), c_size_t)
120 call glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, int(stride, c_int), offset)
121 call glEnableVertexAttribArray(1)
122
123 ! Color: location 2, 4 floats, offset 16 bytes
124 offset = int(4 * c_sizeof(1.0_c_float), c_size_t)
125 call glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, int(stride, c_int), offset)
126 call glEnableVertexAttribArray(2)
127
128 call glBindVertexArray(0)
129
130 ! Allocate vertex buffer
131 allocate(r%vertices(BUFFER_SIZE))
132 r%vertices = 0.0_c_float
133 r%vertex_count = 0
134
135 ! Initialize projection to identity
136 r%projection = 0.0_c_float
137 r%projection(1,1) = 1.0_c_float
138 r%projection(2,2) = 1.0_c_float
139 r%projection(3,3) = 1.0_c_float
140 r%projection(4,4) = 1.0_c_float
141
142 r%initialized = .true.
143
144 end function renderer_create
145
146 ! Destroy renderer
147 subroutine renderer_destroy(r)
148 type(renderer_t), intent(inout) :: r
149 integer :: arr(1)
150
151 if (r%vbo > 0) then
152 arr(1) = r%vbo
153 call glDeleteBuffers(1, arr)
154 r%vbo = 0
155 end if
156
157 if (r%vao > 0) then
158 arr(1) = r%vao
159 call glDeleteVertexArrays(1, arr)
160 r%vao = 0
161 end if
162
163 call shader_destroy(r%shader)
164 call atlas_destroy(r%atlas)
165 call font_destroy(r%font)
166
167 if (allocated(r%vertices)) deallocate(r%vertices)
168 r%initialized = .false.
169 end subroutine renderer_destroy
170
171 ! Begin a new frame (reset vertex buffer)
172 subroutine renderer_begin(r)
173 type(renderer_t), intent(inout) :: r
174 r%vertex_count = 0
175 end subroutine renderer_begin
176
177 ! Set orthographic projection matrix
178 subroutine renderer_set_projection(r, width, height)
179 type(renderer_t), intent(inout) :: r
180 integer, intent(in) :: width, height
181
182 ! Orthographic projection: (0,0) top-left, (width, height) bottom-right
183 r%projection = 0.0_c_float
184 r%projection(1,1) = 2.0_c_float / real(width, c_float)
185 r%projection(2,2) = -2.0_c_float / real(height, c_float) ! Flip Y
186 r%projection(3,3) = -1.0_c_float
187 r%projection(4,4) = 1.0_c_float
188 r%projection(1,4) = -1.0_c_float
189 r%projection(2,4) = 1.0_c_float
190 end subroutine renderer_set_projection
191
192 ! Draw a single character at position (x, y)
193 subroutine renderer_draw_char(r, x, y, codepoint, red, green, blue, alpha)
194 type(renderer_t), intent(inout) :: r
195 real, intent(in) :: x, y
196 integer, intent(in) :: codepoint
197 real, intent(in) :: red, green, blue, alpha
198 type(glyph_t) :: g
199 real(c_float) :: x0, y0, x1, y1
200 real(c_float) :: u0, v0, u1, v1
201 integer :: base
202
203 if (.not. r%initialized) return
204
205 g = atlas_get_glyph(r%atlas, codepoint)
206 if (.not. g%valid) return
207
208 ! Check if buffer is full
209 if (r%vertex_count + VERTICES_PER_QUAD > MAX_VERTICES) then
210 call renderer_flush(r)
211 end if
212
213 ! Calculate quad positions
214 ! x, y is the cursor position (baseline)
215 x0 = real(x, c_float) + real(g%bearing_x, c_float)
216 y0 = real(y, c_float) - real(g%bearing_y, c_float)
217 x1 = x0 + real(g%width, c_float)
218 y1 = y0 + real(g%height, c_float)
219
220 u0 = g%u0
221 v0 = g%v0
222 u1 = g%u1
223 v1 = g%v1
224
225 ! Add 6 vertices (2 triangles) for the quad
226 base = r%vertex_count * FLOATS_PER_VERTEX + 1
227
228 ! Triangle 1: top-left, bottom-left, bottom-right
229 ! Vertex 1: top-left
230 r%vertices(base:base+7) = [x0, y0, u0, v0, &
231 real(red,c_float), real(green,c_float), &
232 real(blue,c_float), real(alpha,c_float)]
233 base = base + 8
234
235 ! Vertex 2: bottom-left
236 r%vertices(base:base+7) = [x0, y1, u0, v1, &
237 real(red,c_float), real(green,c_float), &
238 real(blue,c_float), real(alpha,c_float)]
239 base = base + 8
240
241 ! Vertex 3: bottom-right
242 r%vertices(base:base+7) = [x1, y1, u1, v1, &
243 real(red,c_float), real(green,c_float), &
244 real(blue,c_float), real(alpha,c_float)]
245 base = base + 8
246
247 ! Triangle 2: top-left, bottom-right, top-right
248 ! Vertex 4: top-left
249 r%vertices(base:base+7) = [x0, y0, u0, v0, &
250 real(red,c_float), real(green,c_float), &
251 real(blue,c_float), real(alpha,c_float)]
252 base = base + 8
253
254 ! Vertex 5: bottom-right
255 r%vertices(base:base+7) = [x1, y1, u1, v1, &
256 real(red,c_float), real(green,c_float), &
257 real(blue,c_float), real(alpha,c_float)]
258 base = base + 8
259
260 ! Vertex 6: top-right
261 r%vertices(base:base+7) = [x1, y0, u1, v0, &
262 real(red,c_float), real(green,c_float), &
263 real(blue,c_float), real(alpha,c_float)]
264
265 r%vertex_count = r%vertex_count + VERTICES_PER_QUAD
266
267 end subroutine renderer_draw_char
268
269 ! Draw a string at position (x, y)
270 subroutine renderer_draw_string(r, x, y, text, red, green, blue, alpha)
271 type(renderer_t), intent(inout) :: r
272 real, intent(in) :: x, y
273 character(len=*), intent(in) :: text
274 real, intent(in) :: red, green, blue, alpha
275 integer :: i, cp
276 real :: cursor_x
277 type(glyph_t) :: g
278
279 cursor_x = x
280 do i = 1, len_trim(text)
281 cp = ichar(text(i:i))
282 call renderer_draw_char(r, cursor_x, y, cp, red, green, blue, alpha)
283
284 ! Advance cursor
285 g = atlas_get_glyph(r%atlas, cp)
286 if (g%valid) then
287 cursor_x = cursor_x + real(g%advance)
288 end if
289 end do
290 end subroutine renderer_draw_string
291
292 ! Flush the vertex buffer to GPU and draw
293 subroutine renderer_flush(r)
294 type(renderer_t), intent(inout) :: r
295 integer(c_size_t) :: data_size
296
297 if (r%vertex_count == 0) return
298
299 ! Enable blending for text
300 call glEnable(GL_BLEND)
301 call glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
302
303 ! Bind shader and set uniforms
304 call shader_use(r%shader)
305 call shader_set_projection(r%shader, r%projection)
306
307 ! Bind texture
308 call glActiveTexture(GL_TEXTURE0)
309 call glBindTexture(GL_TEXTURE_2D, r%atlas%texture_id)
310 call glUniform1i(r%shader%atlas_loc, 0)
311
312 ! Upload vertex data
313 call glBindVertexArray(r%vao)
314 call glBindBuffer(GL_ARRAY_BUFFER, r%vbo)
315
316 data_size = int(r%vertex_count * FLOATS_PER_VERTEX * c_sizeof(1.0_c_float), c_size_t)
317 call glBufferSubData_floats(GL_ARRAY_BUFFER, 0_c_size_t, data_size, r%vertices)
318
319 ! Draw
320 call glDrawArrays(GL_TRIANGLES, 0, r%vertex_count)
321
322 ! Unbind
323 call glBindVertexArray(0)
324 call glBindTexture(GL_TEXTURE_2D, 0)
325
326 ! Reset vertex count
327 r%vertex_count = 0
328
329 end subroutine renderer_flush
330
331 end module renderer_mod
332