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