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