8000 miniquad/src/graphics/gl.rs at v0.4.0 · ricande/miniquad · GitHub
[go: up one dir, main page]

Skip to content

Latest commit

 

History

History
1507 lines (1337 loc) · 50.5 KB

gl.rs

File metadata and controls

1507 lines (1337 loc) · 50.5 KB
use std::ffi::CString;
use crate::window;
mod cache;
use super::*;
use cache::*;
/// Raw OpenGL bindings
/// Highly unsafe, some of the functions could be missing due to incompatible GL version
/// or all of them might be missing alltogether if rendering context is not a GL one.
pub mod raw_gl {
use super::*;
#[doc(inline)]
pub use crate::native::gl::*;
pub fn texture_format_into_gl(format: TextureFormat) -> (GLenum, GLenum, GLenum) {
format.into()
}
}
#[derive(Clone, Copy, Debug)]
struct Buffer {
gl_buf: GLuint,
buffer_type: BufferType,
size: usize,
// Dimension of the indices for this buffer,
// used only as a type argument for glDrawElements and can be
// 1, 2 or 4
index_type: Option<u32>,
}
#[derive(Debug)]
struct ShaderUniform {
gl_loc: UniformLocation,
uniform_type: UniformType,
array_count: i32,
}
struct ShaderInternal {
program: GLuint,
images: Vec<ShaderImage>,
uniforms: Vec<ShaderUniform>,
}
#[derive(Clone, Copy, Debug)]
struct Texture {
raw: GLuint,
params: TextureParams,
}
/// Converts from TextureFormat to (internal_format, format, pixel_type)
impl From<TextureFormat> for (GLenum, GLenum, GLenum) {
fn from(format: TextureFormat) -> Self {
match format {
TextureFormat::RGB8 => (GL_RGB, GL_RGB, GL_UNSIGNED_BYTE),
TextureFormat::RGBA8 => (GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE),
TextureFormat::RGBA16F => (GL_RGBA16F, GL_RGBA, GL_FLOAT),
TextureFormat::Depth => (GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT),
TextureFormat::Depth32 => (GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_FLOAT),
#[cfg(target_arch = "wasm32")]
TextureFormat::Alpha => (GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE),
#[cfg(not(target_arch = "wasm32"))]
TextureFormat::Alpha => (GL_R8, GL_RED, GL_UNSIGNED_BYTE), // texture updates will swizzle Red -> Alpha to match WASM
}
}
}
impl From<TextureKind> for GLuint {
fn from(kind: TextureKind) -> GLuint {
match kind {
TextureKind::Texture2D => GL_TEXTURE_2D,
TextureKind::CubeMap => GL_TEXTURE_CUBE_MAP,
}
}
}
impl From<Equation> for GLenum {
fn from(eq: Equation) -> Self {
match eq {
Equation::Add => GL_FUNC_ADD,
Equation::Subtract => GL_FUNC_SUBTRACT,
Equation::ReverseSubtract => GL_FUNC_REVERSE_SUBTRACT,
}
}
}
impl From<BlendFactor> for GLenum {
fn from(factor: BlendFactor) -> GLenum {
match factor {
BlendFactor::Zero => GL_ZERO,
BlendFactor:: GL_ONE,
BlendFactor::Value(BlendValue::SourceColor) => GL_SRC_COLOR,
BlendFactor::Value(BlendValue::SourceAlpha) => GL_SRC_ALPHA,
BlendFactor::Value(BlendValue::DestinationColor) => GL_DST_COLOR,
BlendFactor::Value(BlendValue::DestinationAlpha) => GL_DST_ALPHA,
BlendFactor::OneMinusValue(BlendValue::SourceColor) => GL_ONE_MINUS_SRC_COLOR,
BlendFactor::OneMinusValue(BlendValue::SourceAlpha) => GL_ONE_MINUS_SRC_ALPHA,
BlendFactor::OneMinusValue(BlendValue::DestinationColor) => GL_ONE_MINUS_DST_COLOR,
BlendFactor::OneMinusValue(BlendValue::DestinationAlpha) => GL_ONE_MINUS_DST_ALPHA,
BlendFactor::SourceAlphaSaturate => GL_SRC_ALPHA_SATURATE,
}
}
}
impl From<StencilOp> for GLenum {
fn from(op: StencilOp) -> Self {
match op {
StencilOp::Keep => GL_KEEP,
StencilOp::Zero => GL_ZERO,
StencilOp::Replace => GL_REPLACE,
StencilOp::IncrementClamp => GL_INCR,
StencilOp::DecrementClamp => GL_DECR,
StencilOp::Invert => GL_INVERT,
StencilOp::IncrementWrap => GL_INCR_WRAP,
StencilOp::DecrementWrap => GL_DECR_WRAP,
}
}
}
impl From<CompareFunc> for GLenum {
fn from(cf: CompareFunc) -> Self {
match cf {
CompareFunc::Always => GL_ALWAYS,
CompareFunc::Never => GL_NEVER,
CompareFunc::Less => GL_LESS,
CompareFunc::Equal => GL_EQUAL,
CompareFunc::LessOrEqual => GL_LEQUAL,
CompareFunc::Greater => GL_GREATER,
CompareFunc::NotEqual => GL_NOTEQUAL,
CompareFunc::GreaterOrEqual => GL_GEQUAL,
}
}
}
impl Texture {
pub fn new(
ctx: &mut GlContext,
_access: TextureAccess,
source: TextureSource,
params: TextureParams,
) -> Texture {
if let TextureSource::Bytes(bytes_data) = source {
assert_eq!(
params.format.size(params.width, params.height) as usize,
bytes_data.len()
);
}
let (internal_format, format, pixel_type) = params.format.into();
ctx.cache.store_texture_binding(0);
let mut texture: GLuint = 0;
unsafe {
glGenTextures(1, &mut texture as *mut _);
ctx.cache.bind_texture(0, params.kind.into(), texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // miniquad always uses row alignment of 1
if cfg!(not(target_arch = "wasm32")) {
// if not WASM
if params.format == TextureFormat::Alpha {
// if alpha miniquad texture, the value on non-WASM is stored in red channel
// swizzle red -> alpha
glTexParameteri(params.kind.into(), GL_TEXTURE_SWIZZLE_A, GL_RED as _);
} else {
// keep alpha -> alpha
glTexParameteri(params.kind.into(), GL_TEXTURE_SWIZZLE_A, GL_ALPHA as _);
}
}
match source {
TextureSource::Empty => {
// not quite sure if glTexImage2D(null) is really a requirement
// but it was like this for quite a while and apparantly it works?
glTexImage2D(
GL_TEXTURE_2D,
0,
internal_format as i32,
params.width as i32,
params.height as i32,
0,
format,
pixel_type,
std::ptr::null() as _,
);
}
TextureSource::Bytes(source) => {
assert!(params.kind == TextureKind::Texture2D, "incompatible TextureKind and TextureSource. Cubemaps require TextureSource::Array of 6 textures.");
glTexImage2D(
GL_TEXTURE_2D,
0,
internal_format as i32,
params.width as i32,
params.height as i32,
0,
format,
pixel_type,
source.as_ptr() as *const _,
);
}
TextureSource::Array(array) => {
if params.kind == TextureKind::CubeMap {
assert!(
array.len() == 6,
"Cubemaps require TextureSource::Array of 6 textures."
);
}
for (cubemap_face, mipmaps) in array.iter().enumerate() {
if mipmaps.len() != 1 {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, array.len() as _);
}
for (mipmap_level, bytes) in mipmaps.iter().enumerate() {
let target = match params.kind {
TextureKind::Texture2D => GL_TEXTURE_2D,
TextureKind::CubeMap => {
GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face as u32
}
};
glTexImage2D(
target,
mipmap_level as _,
internal_format as i32,
params.width as i32,
params.height as i32,
0,
format,
pixel_type,
bytes.as_ptr() as *const _,
);
}
}
}
}
let wrap = match params.wrap {
TextureWrap::Repeat => GL_REPEAT,
TextureWrap::Mirror => GL_MIRRORED_REPEAT,
TextureWrap::Clamp => GL_CLAMP_TO_EDGE,
};
let min_filter = Self::gl_filter(params.min_filter, params.mipmap_filter);
let mag_filter = match params.mag_filter {
FilterMode::Nearest => GL_NEAREST,
FilterMode::Linear => GL_LINEAR,
};
glTexParameteri(params.kind.into(), GL_TEXTURE_WRAP_S, wrap as i32);
glTexParameteri(params.kind.into(), GL_TEXTURE_WRAP_T, wrap as i32);
glTexParameteri(params.kind.into(), GL_TEXTURE_MIN_FILTER, min_filter as i32);
glTexParameteri(params.kind.into(), GL_TEXTURE_MAG_FILTER, mag_filter as i32);
}
ctx.cache.restore_texture_binding(0);
Texture {
raw: texture,
params,
}
}
pub fn delete(&self) {
unsafe {
glDeleteTextures(1, &self.raw as *const _);
}
}
pub fn resize(&mut self, ctx: &mut GlContext, width: u32, height: u32, source: Option<&[u8]>) {
ctx.cache.store_texture_binding(0);
ctx.cache.bind_texture(0, self.params.kind.into(), self.raw);
let (internal_format, format, pixel_type) = self.params.format.into();
self.params.width = width;
self.params.height = height;
unsafe {
glTexImage2D(
GL_TEXTURE_2D,
0,
internal_format as i32,
self.params.width as i32,
self.params.height as i32,
0,
format,
pixel_type,
match source {
Some(source) => source.as_ptr() as *const _,
Option::None => std::ptr::null(),
},
);
}
ctx.cache.restore_texture_binding(0);
}
pub fn update_texture_part(
&self,
ctx: &mut GlContext,
x_offset: i32,
y_offset: i32,
width: i32,
height: i32,
source: &[u8],
) {
assert_eq!(self.size(width as _, height as _), source.len());
assert!(x_offset + width <= self.params.width as _);
assert!(y_offset + height <= self.params.height as _);
ctx.cache.store_texture_binding(0);
ctx.cache.bind_texture(0, self.params.kind.into(), self.raw);
let (_, format, pixel_type) = self.params.format.into();
unsafe {
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // miniquad always uses row alignment of 1
if cfg!(not(target_arch = "wasm32")) {
// if not WASM
if self.params.format == TextureFormat::Alpha {
// if alpha miniquad texture, the value on non-WASM is stored in red channel
// swizzle red -> alpha
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_RED as _);
} else {
// keep alpha -> alpha
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ALPHA as _);
}
}
glTexSubImage2D(
GL_TEXTURE_2D,
0,
x_offset as _,
y_offset as _,
width as _,
height as _,
format,
pixel_type,
source.as_ptr() as *const _,
);
}
ctx.cache.restore_texture_binding(0);
}
/// Read texture data into CPU memory
pub fn read_pixels(&self, bytes: &mut [u8]) {
let (_, format, pixel_type) = self.params.format.into();
let mut fbo = 0;
unsafe {
let mut binded_fbo: i32 = 0;
glGetIntegerv(gl::GL_DRAW_FRAMEBUFFER_BINDING, &mut binded_fbo);
glGenFramebuffers(1, &mut fbo);
glBindFramebuffer(gl::GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(
gl::GL_FRAMEBUFFER,
gl::GL_COLOR_ATTACHMENT0,
gl::GL_TEXTURE_2D,
self.raw,
0,
);
glReadPixels(
0,
0,
self.params.width as _,
self.params.height as _,
format,
pixel_type,
bytes.as_mut_ptr() as _,
);
glBindFramebuffer(gl::GL_FRAMEBUFFER, binded_fbo as _);
glDeleteFramebuffers(1, &fbo);
}
}
#[inline]
fn size(&self, width: u32, height: u32) -> usize {
self.params.format.size(width, height) as usize
}
fn gl_filter(filter: FilterMode, mipmap_filter: MipmapFilterMode) -> GLenum {
match filter {
FilterMode::Nearest => match mipmap_filter {
MipmapFilterMode::None => GL_NEAREST,
MipmapFilterMode::Nearest => GL_NEAREST_MIPMAP_NEAREST,
MipmapFilterMode::Linear => GL_NEAREST_MIPMAP_LINEAR,
},
FilterMode::Linear => match mipmap_filter {
MipmapFilterMode::None => GL_LINEAR,
MipmapFilterMode::Nearest => GL_LINEAR_MIPMAP_NEAREST,
MipmapFilterMode::Linear => GL_LINEAR_MIPMAP_LINEAR,
},
}
}
}
pub(crate) struct PipelineInternal {
layout: Vec<Option<VertexAttributeInternal>>,
shader: ShaderId,
params: PipelineParams,
}
type UniformLocation = Option<GLint>;
pub struct ShaderImage {
gl_loc: UniformLocation,
}
fn get_uniform_location(program: GLuint, name: &str) -> Option<i32> {
let cname = CString::new(name).unwrap_or_else(|e| panic!("{}", e));
let location = unsafe { glGetUniformLocation(program, cname.as_ptr()) };
if location == -1 {
return None;
}
Some(location)
}
pub(crate) struct RenderPassInternal {
gl_fb: GLuint,
color_textures: Vec<TextureId>,
depth_texture: Option<TextureId>,
}
struct Textures(Vec<Texture>);
impl Textures {
fn get(&self, texture: TextureId) -> Texture {
match texture.0 {
TextureIdInner::Raw(RawId::OpenGl(texture)) => Texture {
raw: texture,
params: Default::default(),
},
#[cfg(target_vendor = "apple")]
TextureIdInner::Raw(RawId::Metal(..)) => panic!("Metal texture in OpenGL context!"),
TextureIdInner::Managed(texture) => self.0[texture],
}
}
}
pub struct GlContext {
shaders: Vec<ShaderInternal>,
pipelines: Vec<PipelineInternal>,
passes: Vec<RenderPassInternal>,
buffers: Vec<Buffer>,
textures: Textures,
default_framebuffer: GLuint,
pub(crate) cache: GlCache,
pub(crate) features: Features,
}
impl GlContext {
pub fn new() -> GlContext {
unsafe {
let mut default_framebuffer: GLuint = 0;
glGetIntegerv(
GL_FRAMEBUFFER_BINDING,
&mut default_framebuffer as *mut _ as *mut _,
);
let mut vao = 0;
glGenVertexArrays(1, &mut vao as *mut _);
glBindVertexArray(vao);
GlContext {
default_framebuffer,
shaders: vec![],
pipelines: vec![],
passes: vec![],
buffers: vec![],
textures: Textures(vec![]),
features: Features {
instancing: !crate::native::gl::is_gl2(),
..Default::default()
},
cache: GlCache {
stored_index_buffer: 0,
stored_index_type: None,
stored_vertex_buffer: 0,
index_buffer: 0,
index_type: None,
vertex_buffer: 0,
cur_pipeline: None,
color_blend: None,
alpha_blend: None,
stencil: None,
color_write: (true, true, true, true),
cull_face: CullFace::Nothing,
stored_texture: 0,
stored_target: 0,
textures: [CachedTexture {
target: 0,
texture: 0,
}; MAX_SHADERSTAGE_IMAGES],
attributes: [None; MAX_VERTEX_ATTRIBUTES],
},
}
}
}
pub fn features(&self) -> &Features {
&self.features
}
}
fn load_shader_internal(
vertex_shader: &str,
fragment_shader: &str,
meta: ShaderMeta,
) -> Result<ShaderInternal, ShaderError> {
unsafe {
let vertex_shader = load_shader(GL_VERTEX_SHADER, vertex_shader)?;
let fragment_shader = load_shader(GL_FRAGMENT_SHADER, fragment_shader)?;
let program = glCreateProgram();
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
glLinkProgram(program);
let mut link_status = 0;
glGetProgramiv(program, GL_LINK_STATUS, &mut link_status as *mut _);
if link_status == 0 {
let mut max_length: i32 = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &mut max_length as *mut _);
let mut error_message = vec![0u8; max_length as usize + 1];
glGetProgramInfoLog(
program,
max_length,
&mut max_length as *mut _,
error_message.as_mut_ptr() as *mut _,
);
assert!(max_length >= 1);
let error_message =
std::string::String::from_utf8_lossy(&error_message[0..max_length as usize - 1]);
return Err(ShaderError::LinkError(error_message.to_string()));
}
glUseProgram(program);
#[rustfmt::skip]
let images = meta.images.iter().map(|name| ShaderImage {
gl_loc: get_uniform_location(program, name),
}).collect();
#[rustfmt::skip]
let uniforms = meta.uniforms.uniforms.iter().scan(0, |offset, uniform| {
let res = ShaderUniform {
gl_loc: get_uniform_location(program, &uniform.name),
uniform_type: uniform.uniform_type,
array_count: uniform.array_count as _,
};
*offset += uniform.uniform_type.size() * uniform.array_count;
Some(res)
}).collect();
Ok(ShaderInternal {
program,
images,
uniforms,
})
}
}
pub fn load_shader(shader_type: GLenum, source: &str) -> Result<GLuint, ShaderError> {
unsafe {
let shader = glCreateShader(shader_type);
assert!(shader != 0);
let cstring = CString::new(source)?;
let csource = [cstring];
glShaderSource(shader, 1, csource.as_ptr() as *const _, std::ptr::null());
glCompileShader(shader);
let mut is_compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &mut is_compiled as *mut _);
if is_compiled == 0 {
let mut max_length: i32 = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &mut max_length as *mut _);
let mut error_message = vec![0u8; max_length as usize + 1];
glGetShaderInfoLog(
shader,
max_length,
&mut max_length as *mut _,
error_message.as_mut_ptr() as *mut _,
);
assert!(max_length >= 1);
let mut error_message =
std::string::String::from_utf8_lossy(&error_message[0..max_length as usize - 1])
.into_owned();
// On Wasm + Chrome, for unknown reason, string with zero-terminator is returned. On Firefox there is no zero-terminators in JavaScript string.
if error_message.ends_with('\0') {
error_message.pop();
}
return Err(ShaderError::CompilationError {
shader_type: match shader_type {
GL_VERTEX_SHADER => ShaderType::Vertex,
GL_FRAGMENT_SHADER => ShaderType::Fragment,
_ => unreachable!(),
},
error_message,
});
}
Ok(shader)
}
}
impl GlContext {
fn set_blend(&mut self, color_blend: Option<BlendState>, alpha_blend: Option<BlendState>) {
if color_blend.is_none() && alpha_blend.is_some() {
panic!("AlphaBlend without ColorBlend");
}
if self.cache.color_blend == color_blend && self.cache.alpha_blend == alpha_blend {
return;
}
unsafe {
if let Some(color_blend) = color_blend {
if self.cache.color_blend.is_none() {
glEnable(GL_BLEND);
}
let BlendState {
equation: eq_rgb,
sfactor: src_rgb,
dfactor: dst_rgb,
} = color_blend;
if let Some(BlendState {
equation: eq_alpha,
sfactor: src_alpha,
dfactor: dst_alpha,
}) = alpha_blend
{
glBlendFuncSeparate(
src_rgb.into(),
dst_rgb.into(),
src_alpha.into(),
dst_alpha.into(),
);
glBlendEquationSeparate(eq_rgb.into(), eq_alpha.into());
} else {
glBlendFunc(src_rgb.into(), dst_rgb.into());
glBlendEquationSeparate(eq_rgb.into(), eq_rgb.into());
}
} else if self.cache.color_blend.is_some() {
glDisable(GL_BLEND);
}
}
self.cache.color_blend = color_blend;
self.cache.alpha_blend = alpha_blend;
}
fn set_stencil(&mut self, stencil_test: Option<StencilState>) {
if self.cache.stencil == stencil_test {
return;
}
unsafe {
if let Some(stencil) = stencil_test {
if self.cache.stencil.is_none() {
glEnable(GL_STENCIL_TEST);
}
let front = &stencil.front;
glStencilOpSeparate(
GL_FRONT,
front.fail_op.into(),
front.depth_fail_op.into(),
front.pass_op.into(),
);
glStencilFuncSeparate(
GL_FRONT,
front.test_func.into(),
front.test_ref,
front.test_mask,
);
glStencilMaskSeparate(GL_FRONT, front.write_mask);
let back = &stencil.back;
glStencilOpSeparate(
GL_BACK,
back.fail_op.into(),
back.depth_fail_op.into(),
back.pass_op.into(),
);
glStencilFuncSeparate(
GL_BACK,
back.test_func.into(),
back.test_ref.into(),
back.test_mask,
);
glStencilMaskSeparate(GL_BACK, back.write_mask);
} else if self.cache.stencil.is_some() {
glDisable(GL_STENCIL_TEST);
}
}
self.cache.stencil = stencil_test;
}
fn set_cull_face(&mut self, cull_face: CullFace) {
if self.cache.cull_face == cull_face {
return;
}
match cull_face {
CullFace::Nothing => unsafe {
glDisable(GL_CULL_FACE);
},
CullFace::Front => unsafe {
glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT);
},
CullFace::Back => unsafe {
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
},
}
self.cache.cull_face = cull_face;
}
fn set_color_write(&mut self, color_write: ColorMask) {
if self.cache.color_write == color_write {
return;
}
let (r, g, b, a) = color_write;
unsafe { glColorMask(r as _, g as _, b as _, a as _) }
self.cache.color_write = color_write;
}
}
impl RenderingBackend for GlContext {
fn info(&self) -> ContextInfo {
let version_string = unsafe { glGetString(super::gl::GL_VERSION) };
let gl_version_string = unsafe { std::ffi::CStr::from_ptr(version_string as _) }
.to_str()
.unwrap()
.to_string();
let gles3 = gl_version_string.contains("OpenGL ES 3");
let gles2 = !gles3 && gl_version_string.contains("OpenGL ES");
let mut glsl_support = GlslSupport::default();
// this is not quite documented,
// but somehow even GL2.1 usually have all the compatibility extensions to support glsl100
// It was tested on really old windows machines, virtual machines etc. glsl100 always works!
glsl_support.v100 = true;
// on wasm miniquad always creates webgl1 context, with the only glsl available being version 100
#[cfg(target_arch = "wasm32")]
{
// on web, miniquad always loads EXT_shader_texture_lod and OES_standard_derivatives
glsl_support.v100_ext = true;
}
#[cfg(not(target_arch = "wasm32"))]
{
if gles3 {
glsl_support.v300es = true;
}
}
// there is no gl3.4, so 4+ and 3.3 covers all modern OpenGL
if gl_version_string.starts_with("4") || gl_version_string.starts_with("3.3") {
glsl_support.v330 = true;
} else
// gl 3.0, 3.1, 3.2 maps to 1.30, 1.40, 1.50 glsl versions
if gl_version_string.starts_with("3") {
glsl_support.v130 = true;
}
ContextInfo {
backend: Backend::OpenGl,
gl_version_string,
glsl_support,
features: self.features.clone(),
}
}
fn new_shader(
&mut self,
shader: ShaderSource,
meta: ShaderMeta,
) -> Result<ShaderId, ShaderError> {
let (fragment, vertex) = match shader {
ShaderSource::Glsl { fragment, vertex } => (fragment, vertex),
_ => panic!("Metal source on OpenGl context"),
};
let shader = load_shader_internal(vertex, fragment, meta)?;
self.shaders.push(shader);
Ok(ShaderId(self.shaders.len() - 1))
}
fn new_texture(
&mut self,
access: TextureAccess,
source: TextureSource,
params: TextureParams,
) -> TextureId {
let texture = Texture::new(self, access, source, params);
self.textures.0.push(texture);
TextureId(TextureIdInner::Managed(self.textures.0.len() - 1))
}
fn delete_texture(&mut self, texture: TextureId) {
let t = self.textures.get(texture);
t.delete();
}
fn texture_set_wrap(&mut self, texture: TextureId, wrap_x: TextureWrap, wrap_y: TextureWrap) {
let t = self.textures.get(texture);
self.cache.store_texture_binding(0);
self.cache.bind_texture(0, t.params.kind.into(), t.raw);
let wrap_x = match wrap_x {
TextureWrap::Repeat => GL_REPEAT,
TextureWrap::Mirror => GL_MIRRORED_REPEAT,
TextureWrap::Clamp => GL_CLAMP_TO_EDGE,
};
let wrap_y = match wrap_y {
TextureWrap::Repeat => GL_REPEAT,
TextureWrap::Mirror => GL_MIRRORED_REPEAT,
TextureWrap::Clamp => GL_CLAMP_TO_EDGE,
};
unsafe {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_x as i32);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_x as i32);
}
self.cache.restore_texture_binding(0);
}
fn texture_set_min_filter(
&mut self,
texture: TextureId,
filter: FilterMode,
mipmap_filter: MipmapFilterMode,
) {
let t = self.textures.get(texture);
self.cache.store_texture_binding(0);
self.cache.bind_texture(0, t.params.kind.into(), t.raw);
let filter = Texture::gl_filter(filter, mipmap_filter);
unsafe {
glTexParameteri(t.params.kind.into(), GL_TEXTURE_MIN_FILTER, filter as i32);
}
self.cache.restore_texture_binding(0);
}
fn texture_set_mag_filter(&mut self, texture: TextureId, filter: FilterMode) {
let t = self.textures.get(texture);
self.cache.store_texture_binding(0);
self.cache.bind_texture(0, t.params.kind.into(), t.raw);
let filter = match filter {
FilterMode::Nearest => GL_NEAREST,
FilterMode::Linear => GL_LINEAR,
};
unsafe {
glTexParameteri(t.params.kind.into(), GL_TEXTURE_MAG_FILTER, filter as i32);
}
self.cache.restore_texture_binding(0);
}
fn texture_resize(
&mut self,
texture: TextureId,
width: u32,
height: u32,
source: Option<&[u8]>,
) {
let mut t = self.textures.get(texture);
t.resize(self, width, height, source);
}
fn texture_read_pixels(&mut self, texture: TextureId, source: &mut [u8]) {
let t = self.textures.get(texture);
t.read_pixels(source);
}
fn texture_generate_mipmaps(&mut self, texture: TextureId) {
let t = self.textures.get(texture);
self.cache.store_texture_binding(0);
self.cache.bind_texture(0, t.params.kind.into(), t.raw);
unsafe {
glGenerateMipmap(t.params.kind.into());
}
self.cache.restore_texture_binding(0);
}
fn texture_update_part(
&mut self,
texture: TextureId,
x_offset: i32,
y_offset: i32,
width: i32,
height: i32,
source: &[u8],
) {
let t = self.textures.get(texture);
t.update_texture_part(self, x_offset, y_offset, width, height, source);
}
fn texture_params(&self, texture: TextureId) -> TextureParams {
let texture = self.textures.get(texture);
texture.params
}
unsafe fn texture_raw_id(&self, texture: TextureId) -> RawId {
let texture = self.textures.get(texture);
RawId::OpenGl(texture.raw)
}
fn new_render_pass_mrt(
&mut self,
color_img: &[TextureId],
depth_img: Option<TextureId>,
) -> RenderPass {
if color_img.is_empty() && depth_img.is_none() {
panic!("Render pass should have at least one non-none target");
}
let mut gl_fb = 0;
unsafe {
glGenFramebuffers(1, &mut gl_fb as *mut _);
glBindFramebuffer(GL_FRAMEBUFFER, gl_fb);
for (i, color_img) in color_img.iter().enumerate() {
glFramebufferTexture2D(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + i as u32,
GL_TEXTURE_2D,
self.textures.get(*color_img).raw,
0,
);
}
if let Some(depth_img) = depth_img {
glFramebufferTexture2D(
GL_FRAMEBUFFER,
GL_DEPTH_ATTACHMENT,
GL_TEXTURE_2D,
self.textures.get(depth_img).raw,
0,
);
}
if color_img.len() > 1 {
let mut attachments = vec![];
for i in 0..color_img.len() {
attachments.push(GL_COLOR_ATTACHMENT0 + i as u32);
}
glDrawBuffers(2, attachments.as_ptr() as _);
}
glBindFramebuffer(GL_FRAMEBUFFER, self.default_framebuffer);
}
let pass = RenderPassInternal {
gl_fb,
color_textures: color_img.to_vec(),
depth_texture: depth_img,
};
self.passes.push(pass);
RenderPass(self.passes.len() - 1)
}
fn render_pass_color_attachments(&self, render_pass: RenderPass) -> &[TextureId] {
&self.passes[render_pass.0].color_textures
}
fn delete_render_pass(&mut self, render_pass: RenderPass) {
let render_pass = &mut self.passes[render_pass.0];
unsafe { glDeleteFramebuffers(1, &mut render_pass.gl_fb as *mut _) }
for color_texture in &render_pass.color_textures {
self.textures.get(*color_texture).delete();
}
if let Some(depth_texture) = render_pass.depth_texture {
self.textures.get(depth_texture).delete();
}
}
fn new_pipeline(
&mut self,
buffer_layout: &[BufferLayout],
attributes: &[VertexAttribute],
shader: ShaderId,
params: PipelineParams,
) -> Pipeline {
#[derive(Clone, Copy, Default)]
struct BufferCacheData {
stride: i32,
offset: i64,
}
let mut buffer_cache: Vec<BufferCacheData> =
vec![BufferCacheData::default(); buffer_layout.len()];
for VertexAttribute {
format,
buffer_index,
0