1use super::GoudGame;
12use crate::core::error::{GoudError, GoudResult};
13use crate::libs::graphics::backend::RenderBackend;
14
15pub use crate::rendering::sprite_batch::SpriteBatchConfig;
17
18pub use crate::libs::graphics::renderer3d::{
20 FogConfig, GridConfig, GridRenderMode, Light, LightType, PrimitiveCreateInfo, PrimitiveType,
21 SkyboxConfig,
22};
23
24pub struct ImmediateRenderState {
34 pub(crate) shader: crate::libs::graphics::backend::types::ShaderHandle,
36 pub(crate) _vertex_buffer: crate::libs::graphics::backend::types::BufferHandle,
38 pub(crate) _index_buffer: crate::libs::graphics::backend::types::BufferHandle,
40 pub(crate) vao: u32,
42 pub(crate) u_projection: i32,
44 pub(crate) u_model: i32,
45 pub(crate) u_color: i32,
46 pub(crate) u_use_texture: i32,
47 pub(crate) u_texture: i32,
48 pub(crate) u_uv_offset: i32,
49 pub(crate) u_uv_scale: i32,
50}
51
52impl GoudGame {
57 pub fn begin_2d_render(&mut self) -> GoudResult<()> {
62 match &mut self.sprite_batch {
63 Some(batch) => {
64 batch.begin();
65 Ok(())
66 }
67 None => Err(GoudError::NotInitialized),
68 }
69 }
70
71 pub fn end_2d_render(&mut self) -> GoudResult<()> {
73 match &mut self.sprite_batch {
74 Some(batch) => batch.end(),
75 None => Err(GoudError::NotInitialized),
76 }
77 }
78
79 pub fn draw_sprites(&mut self) -> GoudResult<()> {
81 let asset_server = self
82 .asset_server
83 .as_ref()
84 .ok_or(GoudError::NotInitialized)?;
85 match &mut self.sprite_batch {
86 Some(batch) => batch.draw_sprites(&self.world, asset_server),
87 None => Err(GoudError::NotInitialized),
88 }
89 }
90
91 #[inline]
93 pub fn render_2d_stats(&self) -> (usize, usize, f32) {
94 match &self.sprite_batch {
95 Some(batch) => batch.stats(),
96 None => (0, 0, 0.0),
97 }
98 }
99
100 #[inline]
102 pub fn has_2d_renderer(&self) -> bool {
103 self.sprite_batch.is_some()
104 }
105}
106
107impl GoudGame {
114 pub fn begin_render(&mut self) -> bool {
116 let backend = match self.render_backend.as_mut() {
117 Some(b) => b,
118 None => return false,
119 };
120 if backend.begin_frame().is_err() {
121 return false;
122 }
123 let (fb_w, fb_h) = self.get_framebuffer_size();
124 unsafe {
126 gl::Viewport(0, 0, fb_w as i32, fb_h as i32);
127 }
128 true
129 }
130
131 pub fn end_render(&mut self) -> bool {
133 match self.render_backend.as_mut() {
134 Some(b) => b.end_frame().is_ok(),
135 None => false,
136 }
137 }
138
139 pub fn set_viewport(&mut self, x: i32, y: i32, width: u32, height: u32) {
141 if let Some(backend) = self.render_backend.as_mut() {
142 backend.set_viewport(x, y, width, height);
143 }
144 }
145
146 pub fn enable_blending(&mut self) {
148 if let Some(backend) = self.render_backend.as_mut() {
149 backend.enable_blending();
150 }
151 }
152
153 pub fn disable_blending(&mut self) {
155 if let Some(backend) = self.render_backend.as_mut() {
156 backend.disable_blending();
157 }
158 }
159
160 pub fn enable_depth_test(&mut self) {
162 if let Some(backend) = self.render_backend.as_mut() {
163 backend.enable_depth_test();
164 }
165 }
166
167 pub fn disable_depth_test(&mut self) {
169 if let Some(backend) = self.render_backend.as_mut() {
170 backend.disable_depth_test();
171 }
172 }
173
174 pub fn clear_depth(&mut self) {
176 if let Some(backend) = self.render_backend.as_mut() {
177 backend.clear_depth();
178 }
179 }
180
181 #[allow(clippy::too_many_arguments)]
183 pub fn draw_sprite(
184 &mut self,
185 texture: u64,
186 x: f32,
187 y: f32,
188 width: f32,
189 height: f32,
190 rotation: f32,
191 r: f32,
192 g: f32,
193 b: f32,
194 a: f32,
195 ) -> bool {
196 self.draw_sprite_rect(
197 texture, x, y, width, height, rotation, 0.0, 0.0, 1.0, 1.0, r, g, b, a,
198 )
199 }
200
201 #[allow(clippy::too_many_arguments)]
203 pub fn draw_sprite_rect(
204 &mut self,
205 texture: u64,
206 x: f32,
207 y: f32,
208 width: f32,
209 height: f32,
210 rotation: f32,
211 src_x: f32,
212 src_y: f32,
213 src_w: f32,
214 src_h: f32,
215 r: f32,
216 g: f32,
217 b: f32,
218 a: f32,
219 ) -> bool {
220 use crate::libs::graphics::backend::types::{PrimitiveTopology, TextureHandle};
221
222 let state = match &self.immediate_state {
223 Some(s) => s,
224 None => return false,
225 };
226
227 let (fb_w, fb_h) = self.get_framebuffer_size();
228 let (win_w, win_h) = self.get_window_size();
229
230 let (shader, vao, u_proj, u_model, u_color, u_use_tex, u_tex, u_uv_off, u_uv_sc) = (
232 state.shader,
233 state.vao,
234 state.u_projection,
235 state.u_model,
236 state.u_color,
237 state.u_use_texture,
238 state.u_texture,
239 state.u_uv_offset,
240 state.u_uv_scale,
241 );
242
243 let backend = match self.render_backend.as_mut() {
244 Some(b) => b,
245 None => return false,
246 };
247
248 unsafe {
250 gl::Viewport(0, 0, fb_w as i32, fb_h as i32);
251 gl::BindVertexArray(vao);
252 }
253
254 let projection = ortho_matrix(0.0, win_w as f32, win_h as f32, 0.0);
255 let model = model_matrix(x, y, width, height, rotation);
256
257 let tex_index = (texture & 0xFFFF_FFFF) as u32;
258 let tex_gen = ((texture >> 32) & 0xFFFF_FFFF) as u32;
259 let tex_handle = TextureHandle::new(tex_index, tex_gen);
260
261 if backend.bind_shader(shader).is_err() {
262 return false;
263 }
264 backend.set_uniform_mat4(u_proj, &projection);
265 backend.set_uniform_mat4(u_model, &model);
266 backend.set_uniform_vec4(u_color, r, g, b, a);
267 backend.set_uniform_int(u_use_tex, 1);
268 backend.set_uniform_int(u_tex, 0);
269 backend.set_uniform_vec2(u_uv_off, src_x, src_y);
270 backend.set_uniform_vec2(u_uv_sc, src_w, src_h);
271
272 if backend.bind_texture(tex_handle, 0).is_err() {
273 return false;
274 }
275 backend
276 .draw_indexed(PrimitiveTopology::Triangles, 6, 0)
277 .is_ok()
278 }
279
280 #[allow(clippy::too_many_arguments)]
282 pub fn draw_quad(
283 &mut self,
284 x: f32,
285 y: f32,
286 width: f32,
287 height: f32,
288 r: f32,
289 g: f32,
290 b: f32,
291 a: f32,
292 ) -> bool {
293 use crate::libs::graphics::backend::types::PrimitiveTopology;
294
295 let state = match &self.immediate_state {
296 Some(s) => s,
297 None => return false,
298 };
299
300 let (fb_w, fb_h) = self.get_framebuffer_size();
301 let (win_w, win_h) = self.get_window_size();
302
303 let (shader, vao, u_proj, u_model, u_color, u_use_tex) = (
304 state.shader,
305 state.vao,
306 state.u_projection,
307 state.u_model,
308 state.u_color,
309 state.u_use_texture,
310 );
311
312 let backend = match self.render_backend.as_mut() {
313 Some(b) => b,
314 None => return false,
315 };
316
317 unsafe {
319 gl::Viewport(0, 0, fb_w as i32, fb_h as i32);
320 gl::BindVertexArray(vao);
321 }
322
323 let projection = ortho_matrix(0.0, win_w as f32, win_h as f32, 0.0);
324 let model = model_matrix(x, y, width, height, 0.0);
325
326 if backend.bind_shader(shader).is_err() {
327 return false;
328 }
329 backend.set_uniform_mat4(u_proj, &projection);
330 backend.set_uniform_mat4(u_model, &model);
331 backend.set_uniform_vec4(u_color, r, g, b, a);
332 backend.set_uniform_int(u_use_tex, 0);
333
334 backend
335 .draw_indexed(PrimitiveTopology::Triangles, 6, 0)
336 .is_ok()
337 }
338
339 pub unsafe fn get_render_stats(
349 &self,
350 out_stats: *mut crate::ffi::renderer::GoudRenderStats,
351 ) -> bool {
352 if out_stats.is_null() {
353 return false;
354 }
355 unsafe {
357 *out_stats = crate::ffi::renderer::GoudRenderStats::default();
358 }
359 true
360 }
361}
362
363fn ortho_matrix(left: f32, right: f32, bottom: f32, top: f32) -> [f32; 16] {
369 let near = -1.0f32;
370 let far = 1.0f32;
371 [
372 2.0 / (right - left),
373 0.0,
374 0.0,
375 0.0,
376 0.0,
377 2.0 / (top - bottom),
378 0.0,
379 0.0,
380 0.0,
381 0.0,
382 -2.0 / (far - near),
383 0.0,
384 -(right + left) / (right - left),
385 -(top + bottom) / (top - bottom),
386 -(far + near) / (far - near),
387 1.0,
388 ]
389}
390
391fn model_matrix(x: f32, y: f32, width: f32, height: f32, rotation: f32) -> [f32; 16] {
393 let cos_r = rotation.cos();
394 let sin_r = rotation.sin();
395 [
396 width * cos_r,
397 width * sin_r,
398 0.0,
399 0.0,
400 -height * sin_r,
401 height * cos_r,
402 0.0,
403 0.0,
404 0.0,
405 0.0,
406 1.0,
407 0.0,
408 x,
409 y,
410 0.0,
411 1.0,
412 ]
413}
414
415#[cfg(test)]
420mod tests {
421 use super::*;
422 use crate::sdk::GameConfig;
423
424 #[test]
425 fn test_begin_2d_render_headless() {
426 let mut game = GoudGame::new(GameConfig::default()).unwrap();
427 assert!(game.begin_2d_render().is_err());
428 }
429
430 #[test]
431 fn test_end_2d_render_headless() {
432 let mut game = GoudGame::new(GameConfig::default()).unwrap();
433 assert!(game.end_2d_render().is_err());
434 }
435
436 #[test]
437 fn test_draw_sprites_headless() {
438 let mut game = GoudGame::new(GameConfig::default()).unwrap();
439 assert!(game.draw_sprites().is_err());
440 }
441
442 #[test]
443 fn test_render_2d_stats_headless() {
444 let game = GoudGame::new(GameConfig::default()).unwrap();
445 assert_eq!(game.render_2d_stats(), (0, 0, 0.0));
446 }
447
448 #[test]
449 fn test_has_2d_renderer_headless() {
450 let game = GoudGame::new(GameConfig::default()).unwrap();
451 assert!(!game.has_2d_renderer());
452 }
453
454 #[test]
455 fn test_draw_sprite_headless_returns_false() {
456 let mut game = GoudGame::new(GameConfig::default()).unwrap();
457 assert!(!game.draw_sprite(0, 0.0, 0.0, 10.0, 10.0, 0.0, 1.0, 1.0, 1.0, 1.0));
458 }
459
460 #[test]
461 fn test_draw_quad_headless_returns_false() {
462 let mut game = GoudGame::new(GameConfig::default()).unwrap();
463 assert!(!game.draw_quad(0.0, 0.0, 10.0, 10.0, 1.0, 0.0, 0.0, 1.0));
464 }
465
466 #[test]
467 fn test_begin_render_headless() {
468 let mut game = GoudGame::new(GameConfig::default()).unwrap();
469 assert!(!game.begin_render());
470 }
471
472 #[test]
473 fn test_end_render_headless() {
474 let mut game = GoudGame::new(GameConfig::default()).unwrap();
475 assert!(!game.end_render());
476 }
477
478 #[test]
479 fn test_ortho_matrix_identity_like() {
480 let m = ortho_matrix(0.0, 2.0, 0.0, 2.0);
481 assert!((m[0] - 1.0).abs() < 0.001);
482 assert!((m[5] - 1.0).abs() < 0.001);
483 }
484
485 #[test]
486 fn test_model_matrix_no_rotation() {
487 let m = model_matrix(10.0, 20.0, 5.0, 5.0, 0.0);
488 assert!((m[12] - 10.0).abs() < 0.001);
489 assert!((m[13] - 20.0).abs() < 0.001);
490 }
491}