mod gpu_buffers;
mod gpu_config;
mod gpu_mesh;
mod gpu_texture;
mod gpu_triangle;
mod gpu_vertex;
mod heap_buffer;
mod render_pipeline;
mod shader;
pub use gpu_config::*;
pub use gpu_mesh::*;
pub use gpu_texture::*;
pub use gpu_triangle::*;
pub use gpu_vertex::*;
pub(super) use heap_buffer::*;
pub use render_pipeline::*;
pub use shader::*;
use self::gpu_buffers::GpuBuffers;
use crate::{PaddleResult, Transform};
use js_sys::Uint16Array;
use web_sys::{WebGlRenderingContext, WebGlShader, WebGlTexture};
pub(super) struct Gpu {
gpu_buffers: GpuBuffers,
default_fragment_shader: WebGlShader,
default_vertex_shader: WebGlShader,
active_render_pipeline: RenderPipelineHandle,
render_pipelines: RenderPipelineContainer,
pub(crate) depth_tests_enabled: bool,
}
impl Gpu {
pub fn new(
gl: &WebGlRenderingContext,
projection: Transform,
config: &GpuConfig,
) -> PaddleResult<Self> {
gl.blend_func_separate(
WebGlRenderingContext::SRC_ALPHA,
WebGlRenderingContext::ONE_MINUS_SRC_ALPHA,
WebGlRenderingContext::ONE,
WebGlRenderingContext::ONE_MINUS_SRC_ALPHA,
);
gl.enable(WebGlRenderingContext::BLEND);
let mut depth_tests_enabled = false;
if config.depth_test {
gl.enable(WebGlRenderingContext::DEPTH_TEST);
depth_tests_enabled = gl.is_enabled(WebGlRenderingContext::DEPTH_TEST);
if depth_tests_enabled {
gl.clear_depth(0.0);
gl.depth_func(WebGlRenderingContext::GEQUAL);
}
}
let default_vertex_shader = new_vertex_shader(&gl, DEFAULT_VERTEX_SHADER)?;
let default_fragment_shader = new_fragment_shader(&gl, DEFAULT_FRAGMENT_SHADER)?;
let render_pipelines = RenderPipelineContainer::new();
let gpu_buffers = GpuBuffers::new(gl)?;
let mut gpu = Self {
gpu_buffers,
default_vertex_shader,
default_fragment_shader,
render_pipelines,
depth_tests_enabled,
active_render_pipeline: Default::default(),
};
gpu.new_render_pipeline(
gl,
gpu.default_vertex_shader.clone(),
gpu.default_fragment_shader.clone(),
VertexDescriptor::default(),
&[("Projection", UniformValue::Matrix3fv(projection.as_slice()))],
)?;
Ok(gpu)
}
pub(super) fn perform_draw_calls(
&mut self,
buffer: &mut WasmHeapBuffer,
gl: &WebGlRenderingContext,
vertices: &[GpuVertex],
triangles: &[GpuTriangle],
) -> PaddleResult<()> {
buffer.prepare_vertices(vertices, self.active_vertex_descriptor());
self.upload_vertices(gl, &buffer.vertex_data);
let mut current_texture: Option<&WebGlTexture> = None;
for triangle in triangles.iter() {
if let Some(img) = vertices[triangle.indices[0] as usize].tex() {
let should_flush = match current_texture {
Some(val) => img != val,
None => true,
};
if should_flush {
self.draw_single_texture(gl, current_texture, &buffer.triangle_indices);
buffer.triangle_indices.clear();
}
current_texture = Some(img);
}
buffer
.triangle_indices
.extend(triangle.indices.iter().map(|n| *n as u16));
}
if !buffer.triangle_indices.is_empty() {
self.draw_single_texture(gl, current_texture, &buffer.triangle_indices);
buffer.triangle_indices.clear();
}
Ok(())
}
fn draw_single_texture(
&mut self,
gl: &WebGlRenderingContext,
texture: Option<&WebGlTexture>,
indices: &[u16],
) {
if indices.is_empty() {
return;
}
self.gpu_buffers.ensure_index_buffer_size(gl, indices.len());
unsafe {
let array = Uint16Array::view(&indices);
gl.buffer_data_with_array_buffer_view(
WebGlRenderingContext::ELEMENT_ARRAY_BUFFER,
&array,
WebGlRenderingContext::STREAM_DRAW,
);
}
gl.active_texture(WebGlRenderingContext::TEXTURE0);
gl.bind_texture(WebGlRenderingContext::TEXTURE_2D, texture);
gl.draw_elements_with_i32(
WebGlRenderingContext::TRIANGLES,
indices.len() as i32,
WebGlRenderingContext::UNSIGNED_SHORT,
0,
);
gl.bind_texture(WebGlRenderingContext::TEXTURE_2D, None);
}
pub fn active_render_pipeline(&self) -> RenderPipelineHandle {
self.active_render_pipeline
}
pub fn active_vertex_descriptor(&self) -> &VertexDescriptor {
self.render_pipelines[self.active_render_pipeline].vertex_descriptor()
}
pub fn update_uniform(
&mut self,
gl: &WebGlRenderingContext,
rp: RenderPipelineHandle,
name: &'static str,
value: &super::gpu::UniformValue,
) {
let stashed_rp = self.active_render_pipeline;
self.use_render_pipeline(gl, rp);
self.render_pipelines[rp].prepare_uniform(gl, name, value);
self.use_render_pipeline(gl, stashed_rp);
}
}
impl Gpu {
pub(super) fn custom_drop(&mut self, gl: &WebGlRenderingContext) {
self.render_pipelines.drop_programs(gl);
self.gpu_buffers.custom_drop(gl);
gl.delete_shader(Some(&self.default_fragment_shader));
gl.delete_shader(Some(&self.default_vertex_shader));
}
}