Fortran · 8068 bytes Raw Blame History
1 module atlas_mod
2 use, intrinsic :: iso_c_binding
3 use glyph_mod
4 use font_mod
5 use gl_bindings
6 implicit none
7 private
8
9 public :: atlas_t
10 public :: atlas_create, atlas_destroy, atlas_get_glyph
11
12 integer, parameter :: ATLAS_SIZE = 1024 ! Larger for Unicode support
13 integer, parameter :: ASCII_START = 32
14 integer, parameter :: ASCII_END = 126
15 integer, parameter :: EXTENDED_CACHE_SIZE = 256 ! Cache for non-ASCII glyphs
16
17 ! Entry for extended glyph cache (simple hash table)
18 type :: cache_entry_t
19 integer :: codepoint = 0
20 type(glyph_t) :: glyph
21 logical :: used = .false.
22 end type cache_entry_t
23
24 type :: atlas_t
25 integer :: texture_id = 0
26 integer :: width = ATLAS_SIZE
27 integer :: height = ATLAS_SIZE
28 integer :: cursor_x = 0
29 integer :: cursor_y = 0
30 integer :: row_height = 0
31 type(glyph_t) :: glyphs(0:127) ! ASCII cache
32 type(cache_entry_t) :: extended(EXTENDED_CACHE_SIZE) ! Extended glyph cache
33 type(font_t), pointer :: font => null() ! Reference for on-demand loading
34 logical :: initialized = .false.
35 end type atlas_t
36
37 contains
38
39 ! Create a texture atlas from a font, pre-rendering ASCII characters
40 function atlas_create(font) result(atlas)
41 type(font_t), intent(inout), target :: font
42 type(atlas_t) :: atlas
43 integer :: tex(1)
44 integer :: cp, i
45 type(glyph_t) :: g
46 type(c_ptr) :: bitmap_ptr
47 integer(c_int8_t), target :: white_pixel(1)
48
49 if (.not. font%loaded) then
50 print *, "Error: Cannot create atlas from unloaded font"
51 return
52 end if
53
54 ! Store font reference for on-demand glyph loading
55 atlas%font => font
56
57 ! Create OpenGL texture
58 call glGenTextures(1, tex)
59 atlas%texture_id = tex(1)
60 call glBindTexture(GL_TEXTURE_2D, atlas%texture_id)
61
62 ! Set texture parameters
63 call glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
64 call glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
65 call glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
66 call glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
67
68 ! Disable byte-alignment restriction for grayscale textures
69 call glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
70
71 ! Allocate empty texture
72 call glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, &
73 atlas%width, atlas%height, 0, &
74 GL_RED, GL_UNSIGNED_BYTE, c_null_ptr)
75
76 ! Add a solid white pixel at position (0,0) for solid rectangle rendering
77 ! This allows renderer_draw_rect to sample a non-transparent pixel
78 white_pixel(1) = -1_c_int8_t ! 255 as signed byte
79 call glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, &
80 GL_RED, GL_UNSIGNED_BYTE, c_loc(white_pixel))
81
82 ! Initialize glyph array
83 do cp = 0, 127
84 call glyph_init(atlas%glyphs(cp))
85 end do
86
87 ! Initialize extended cache
88 do i = 1, EXTENDED_CACHE_SIZE
89 atlas%extended(i)%used = .false.
90 atlas%extended(i)%codepoint = 0
91 call glyph_init(atlas%extended(i)%glyph)
92 end do
93
94 ! Pre-render ASCII printable characters (32-126)
95 atlas%cursor_x = 1
96 atlas%cursor_y = 1
97 atlas%row_height = 0
98
99 do cp = ASCII_START, ASCII_END
100 g = font_render_glyph(font, cp, bitmap_ptr)
101 if (g%valid) then
102 call atlas_add_glyph(atlas, g, bitmap_ptr)
103 atlas%glyphs(cp) = g
104 end if
105 end do
106
107 ! Unbind texture
108 call glBindTexture(GL_TEXTURE_2D, 0)
109
110 atlas%initialized = .true.
111
112 end function atlas_create
113
114 ! Add a rendered glyph to the atlas
115 subroutine atlas_add_glyph(atlas, g, bitmap_ptr)
116 type(atlas_t), intent(inout) :: atlas
117 type(glyph_t), intent(inout) :: g
118 type(c_ptr), intent(in) :: bitmap_ptr
119 integer :: padding
120
121 padding = 1 ! 1 pixel padding between glyphs
122
123 ! Check if glyph fits in current row
124 if (atlas%cursor_x + g%width + padding > atlas%width) then
125 ! Move to next row
126 atlas%cursor_x = 1
127 atlas%cursor_y = atlas%cursor_y + atlas%row_height + padding
128 atlas%row_height = 0
129 end if
130
131 ! Check if glyph fits vertically
132 if (atlas%cursor_y + g%height + padding > atlas%height) then
133 print *, "Warning: Atlas texture is full, cannot add glyph"
134 return
135 end if
136
137 ! Store position in atlas
138 g%tex_x = atlas%cursor_x
139 g%tex_y = atlas%cursor_y
140
141 ! Copy bitmap to texture
142 if (g%width > 0 .and. g%height > 0 .and. c_associated(bitmap_ptr)) then
143 call glTexSubImage2D(GL_TEXTURE_2D, 0, &
144 g%tex_x, g%tex_y, g%width, g%height, &
145 GL_RED, GL_UNSIGNED_BYTE, bitmap_ptr)
146 end if
147
148 ! Calculate UV coordinates (normalized 0-1)
149 g%u0 = real(g%tex_x, c_float) / real(atlas%width, c_float)
150 g%v0 = real(g%tex_y, c_float) / real(atlas%height, c_float)
151 g%u1 = real(g%tex_x + g%width, c_float) / real(atlas%width, c_float)
152 g%v1 = real(g%tex_y + g%height, c_float) / real(atlas%height, c_float)
153
154 ! Update cursor position
155 atlas%cursor_x = atlas%cursor_x + g%width + padding
156 atlas%row_height = max(atlas%row_height, g%height)
157
158 end subroutine atlas_add_glyph
159
160 ! Get glyph information for a codepoint (with on-demand loading for non-ASCII)
161 function atlas_get_glyph(atlas, codepoint) result(g)
162 type(atlas_t), intent(inout) :: atlas
163 integer, intent(in) :: codepoint
164 type(glyph_t) :: g
165
166 if (codepoint >= 0 .and. codepoint <= 127) then
167 g = atlas%glyphs(codepoint)
168 else
169 ! Look up or load extended glyph
170 g = atlas_get_extended_glyph(atlas, codepoint)
171 end if
172 end function atlas_get_glyph
173
174 ! Get or load an extended (non-ASCII) glyph
175 function atlas_get_extended_glyph(atlas, codepoint) result(g)
176 type(atlas_t), intent(inout) :: atlas
177 integer, intent(in) :: codepoint
178 type(glyph_t) :: g
179 integer :: hash_idx, probe, i
180 type(c_ptr) :: bitmap_ptr
181 logical :: used_fallback
182
183 call glyph_init(g)
184
185 ! Simple hash function
186 hash_idx = mod(codepoint, EXTENDED_CACHE_SIZE) + 1
187
188 ! Linear probe to find existing or empty slot
189 do i = 0, EXTENDED_CACHE_SIZE - 1
190 probe = mod(hash_idx + i - 1, EXTENDED_CACHE_SIZE) + 1
191
192 if (.not. atlas%extended(probe)%used) then
193 ! Empty slot - load glyph here
194 call atlas_load_extended_glyph(atlas, codepoint, probe)
195 g = atlas%extended(probe)%glyph
196 return
197 else if (atlas%extended(probe)%codepoint == codepoint) then
198 ! Found cached glyph
199 g = atlas%extended(probe)%glyph
200 return
201 end if
202 end do
203
204 ! Cache is full - just render without caching (fallback behavior)
205 if (associated(atlas%font)) then
206 g = font_render_glyph_with_fallback(atlas%font, codepoint, bitmap_ptr, used_fallback)
207 end if
208
209 end function atlas_get_extended_glyph
210
211 ! Load an extended glyph into the cache at specified slot
212 subroutine atlas_load_extended_glyph(atlas, codepoint, slot)
213 type(atlas_t), intent(inout) :: atlas
214 integer, intent(in) :: codepoint, slot
215 type(glyph_t) :: g
216 type(c_ptr) :: bitmap_ptr
217 logical :: used_fallback
218
219 if (.not. associated(atlas%font)) return
220
221 ! Render using fallback support
222 g = font_render_glyph_with_fallback(atlas%font, codepoint, bitmap_ptr, used_fallback)
223
224 if (g%valid) then
225 ! Bind texture and add glyph
226 call glBindTexture(GL_TEXTURE_2D, atlas%texture_id)
227 call atlas_add_glyph(atlas, g, bitmap_ptr)
228 call glBindTexture(GL_TEXTURE_2D, 0)
229
230 ! Store in cache
231 atlas%extended(slot)%codepoint = codepoint
232 atlas%extended(slot)%glyph = g
233 atlas%extended(slot)%used = .true.
234 end if
235
236 end subroutine atlas_load_extended_glyph
237
238 ! Destroy atlas and free texture
239 subroutine atlas_destroy(atlas)
240 type(atlas_t), intent(inout) :: atlas
241 integer :: tex(1)
242
243 if (atlas%texture_id > 0) then
244 tex(1) = atlas%texture_id
245 call glDeleteTextures(1, tex)
246 atlas%texture_id = 0
247 end if
248
249 atlas%initialized = .false.
250 end subroutine atlas_destroy
251
252 end module atlas_mod
253