#![allow(missing_docs)]
#![deny(missing_copy_implementations)]
#[macro_use]
extern crate log;
extern crate libc;
extern crate gfx_gl as gl;
extern crate gfx;
use std::cell::RefCell;
use std::rc::Rc;
use gfx::device as d;
use gfx::device::attrib::*;
use gfx::device::draw::{Access, Gamma, Target};
use gfx::device::handle;
use gfx::device::state::{CullFace, RasterMethod, FrontFace};
pub use self::draw::{Command, CommandBuffer};
pub use self::factory::{Factory, Output};
pub use self::info::{Info, PlatformName, Version};
mod draw;
mod factory;
mod shade;
mod state;
mod tex;
mod info;
pub type Buffer = gl::types::GLuint;
pub type ArrayBuffer = gl::types::GLuint;
pub type Shader = gl::types::GLuint;
pub type Program = gl::types::GLuint;
pub type FrameBuffer = gl::types::GLuint;
pub type Surface = gl::types::GLuint;
pub type Sampler = gl::types::GLuint;
pub type Texture = gl::types::GLuint;
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub enum Resources {}
impl gfx::Resources for Resources {
type Buffer = Buffer;
type ArrayBuffer = ArrayBuffer;
type Shader = Shader;
type Program = Program;
type FrameBuffer = FrameBuffer;
type Surface = Surface;
type Texture = Texture;
type Sampler = Sampler;
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Error {
NoError,
InvalidEnum,
InvalidValue,
InvalidOperation,
InvalidFramebufferOperation,
OutOfMemory,
UnknownError,
}
impl Error {
pub fn from_error_code(error_code: gl::types::GLenum) -> Error {
match error_code {
gl::NO_ERROR => Error::NoError,
gl::INVALID_ENUM => Error::InvalidEnum,
gl::INVALID_VALUE => Error::InvalidValue,
gl::INVALID_OPERATION => Error::InvalidOperation,
gl::INVALID_FRAMEBUFFER_OPERATION => Error::InvalidFramebufferOperation,
gl::OUT_OF_MEMORY => Error::OutOfMemory,
_ => Error::UnknownError,
}
}
}
const RESET_CB: [Command; 11] = [
Command::BindProgram(0),
Command::BindArrayBuffer(0),
Command::BindIndex(0),
Command::BindFrameBuffer(Access::Draw, 0, Gamma::Original),
Command::BindFrameBuffer(Access::Read, 0, Gamma::Original),
Command::SetPrimitiveState(d::state::Primitive {
front_face: FrontFace::CounterClockwise,
method: RasterMethod::Fill(CullFace::Back),
offset: None,
}),
Command::SetViewport(d::target::Rect{x: 0, y: 0, w: 0, h: 0}),
Command::SetScissor(None),
Command::SetDepthStencilState(None, None, CullFace::Nothing),
Command::SetBlendState(None),
Command::SetColorMask(d::state::MASK_ALL),
];
fn primitive_to_gl(prim_type: d::PrimitiveType) -> gl::types::GLenum {
match prim_type {
d::PrimitiveType::Point => gl::POINTS,
d::PrimitiveType::Line => gl::LINES,
d::PrimitiveType::LineStrip => gl::LINE_STRIP,
d::PrimitiveType::TriangleList => gl::TRIANGLES,
d::PrimitiveType::TriangleStrip => gl::TRIANGLE_STRIP,
d::PrimitiveType::TriangleFan => gl::TRIANGLE_FAN,
}
}
fn access_to_gl(access: Access) -> gl::types::GLenum {
match access {
Access::Draw => gl::DRAW_FRAMEBUFFER,
Access::Read => gl::READ_FRAMEBUFFER,
}
}
fn target_to_gl(target: Target) -> gl::types::GLenum {
match target {
Target::Color(index) => gl::COLOR_ATTACHMENT0 + (index as gl::types::GLenum),
Target::Depth => gl::DEPTH_ATTACHMENT,
Target::Stencil => gl::STENCIL_ATTACHMENT,
Target::DepthStencil => gl::DEPTH_STENCIL_ATTACHMENT,
}
}
pub struct Share {
context: gl::Gl,
capabilities: d::Capabilities,
handles: RefCell<handle::Manager<Resources>>,
main_fbo: handle::FrameBuffer<Resources>,
}
pub struct Device {
info: Info,
share: Rc<Share>,
frame_handles: handle::Manager<Resources>,
max_resource_count: Option<usize>,
}
impl Device {
pub fn new<F>(fn_proc: F) -> Device where
F: FnMut(&str) -> *const ::libc::c_void
{
use gfx::device::handle::Producer;
let gl = gl::Gl::load_with(fn_proc);
let (info, caps) = info::get(&gl);
info!("Vendor: {:?}", info.platform_name.vendor);
info!("Renderer: {:?}", info.platform_name.renderer);
info!("Version: {:?}", info.version);
info!("Shading Language: {:?}", info.shading_language);
debug!("Loaded Extensions:");
for extension in info.extensions.iter() {
debug!("- {}", *extension);
}
unsafe {
gl.PixelStorei(gl::UNPACK_ALIGNMENT, 1);
}
let mut handles = handle::Manager::new();
let main_fbo = handles.make_frame_buffer(0);
Device {
info: info,
share: Rc::new(Share {
context: gl,
capabilities: caps,
handles: RefCell::new(handles),
main_fbo: main_fbo,
}),
frame_handles: handle::Manager::new(),
max_resource_count: Some(999999),
}
}
pub fn spawn_factory(&self) -> Factory {
Factory::new(self.share.clone())
}
pub unsafe fn with_gl<F: FnMut(&gl::Gl)>(&mut self, mut fun: F) {
use gfx::Device;
self.reset_state();
fun(&self.share.context);
}
fn check(&mut self, cmd: &Command) {
if cfg!(not(ndebug)) {
let gl = &self.share.context;
let err = Error::from_error_code(unsafe { gl.GetError() });
if err != Error::NoError {
panic!("Error after executing command {:?}: {:?}", cmd, err);
}
}
}
pub fn get_info<'a>(&'a self) -> &'a Info {
&self.info
}
fn process(&mut self, cmd: &Command, data_buf: &d::draw::DataBuffer) {
match *cmd {
Command::Clear(ref data, mask) => {
let mut flags = 0;
let gl = &self.share.context;
if mask.intersects(d::target::COLOR) {
flags |= gl::COLOR_BUFFER_BIT;
state::bind_color_mask(gl, d::state::MASK_ALL);
let c = data.color;
unsafe { gl.ClearColor(c[0], c[1], c[2], c[3]) };
}
if mask.intersects(d::target::DEPTH) {
flags |= gl::DEPTH_BUFFER_BIT;
unsafe {
gl.DepthMask(gl::TRUE);
gl.ClearDepth(data.depth as gl::types::GLclampd);
}
}
if mask.intersects(d::target::STENCIL) {
flags |= gl::STENCIL_BUFFER_BIT;
unsafe {
gl.StencilMask(gl::types::GLuint::max_value());
gl.ClearStencil(data.stencil as gl::types::GLint);
}
}
unsafe { gl.Clear(flags) };
},
Command::BindProgram(program) => {
let gl = &self.share.context;
unsafe { gl.UseProgram(program) };
},
Command::BindArrayBuffer(array_buffer) => {
let gl = &self.share.context;
if self.share.capabilities.array_buffer_supported {
unsafe { gl.BindVertexArray(array_buffer) };
} else {
error!("Ignored VAO bind command: {}", array_buffer)
}
},
Command::BindAttribute(slot, buffer, format) => {
let gl_type = match format.elem_type {
Type::Int(_, IntSize::U8, SignFlag::Unsigned) => gl::UNSIGNED_BYTE,
Type::Int(_, IntSize::U8, SignFlag::Signed) => gl::BYTE,
Type::Int(_, IntSize::U16, SignFlag::Unsigned) => gl::UNSIGNED_SHORT,
Type::Int(_, IntSize::U16, SignFlag::Signed) => gl::SHORT,
Type::Int(_, IntSize::U32, SignFlag::Unsigned) => gl::UNSIGNED_INT,
Type::Int(_, IntSize::U32, SignFlag::Signed) => gl::INT,
Type::Float(_, FloatSize::F16) => gl::HALF_FLOAT,
Type::Float(_, FloatSize::F32) => gl::FLOAT,
Type::Float(_, FloatSize::F64) => gl::DOUBLE,
_ => {
error!("Unsupported element type: {:?}", format.elem_type);
return
}
};
let gl = &self.share.context;
unsafe { gl.BindBuffer(gl::ARRAY_BUFFER, buffer) };
let offset = format.offset as *const gl::types::GLvoid;
match format.elem_type {
Type::Int(IntSubType::Raw, _, _) => unsafe {
gl.VertexAttribIPointer(slot as gl::types::GLuint,
format.elem_count as gl::types::GLint, gl_type,
format.stride as gl::types::GLint, offset);
},
Type::Int(IntSubType::Normalized, _, _) => unsafe {
gl.VertexAttribPointer(slot as gl::types::GLuint,
format.elem_count as gl::types::GLint, gl_type, gl::TRUE,
format.stride as gl::types::GLint, offset);
},
Type::Int(IntSubType::AsFloat, _, _) => unsafe {
gl.VertexAttribPointer(slot as gl::types::GLuint,
format.elem_count as gl::types::GLint, gl_type, gl::FALSE,
format.stride as gl::types::GLint, offset);
},
Type::Float(FloatSubType::Default, _) => unsafe {
gl.VertexAttribPointer(slot as gl::types::GLuint,
format.elem_count as gl::types::GLint, gl_type, gl::FALSE,
format.stride as gl::types::GLint, offset);
},
Type::Float(FloatSubType::Precision, _) => unsafe {
gl.VertexAttribLPointer(slot as gl::types::GLuint,
format.elem_count as gl::types::GLint, gl_type,
format.stride as gl::types::GLint, offset);
},
_ => ()
}
unsafe { gl.EnableVertexAttribArray(slot as gl::types::GLuint) };
if self.share.capabilities.instance_rate_supported {
unsafe { gl.VertexAttribDivisor(slot as gl::types::GLuint,
format.instance_rate as gl::types::GLuint) };
}else if format.instance_rate != 0 {
error!("Instanced arrays are not supported");
}
},
Command::BindIndex(buffer) => {
let gl = &self.share.context;
unsafe { gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, buffer) };
},
Command::BindFrameBuffer(access, frame_buffer, gamma) => {
let caps = &self.share.capabilities;
let gl = &self.share.context;
if !caps.render_targets_supported {
panic!("Tried to do something with an FBO without FBO support!")
}
let point = access_to_gl(access);
unsafe { gl.BindFramebuffer(point, frame_buffer) };
match (caps.srgb_color_supported, gamma) {
(true, Gamma::Original) => unsafe { gl.Disable(gl::FRAMEBUFFER_SRGB) },
(true, Gamma::Convert) => unsafe { gl.Enable( gl::FRAMEBUFFER_SRGB) },
(false, _) => (),
}
},
Command::UnbindTarget(access, target) => {
if !self.share.capabilities.render_targets_supported {
panic!("Tried to do something with an FBO without FBO support!")
}
let gl = &self.share.context;
let point = access_to_gl(access);
let att = target_to_gl(target);
unsafe { gl.FramebufferRenderbuffer(point, att, gl::RENDERBUFFER, 0) };
},
Command::BindTargetSurface(access, target, name) => {
if !self.share.capabilities.render_targets_supported {
panic!("Tried to do something with an FBO without FBO support!")
}
let gl = &self.share.context;
let point = access_to_gl(access);
let att = target_to_gl(target);
unsafe { gl.FramebufferRenderbuffer(point, att, gl::RENDERBUFFER, name) };
},
Command::BindTargetTexture(access, target, name, level, layer) => {
if !self.share.capabilities.render_targets_supported {
panic!("Tried to do something with an FBO without FBO support!")
}
let gl = &self.share.context;
let point = access_to_gl(access);
let att = target_to_gl(target);
match layer {
Some(layer) => unsafe { gl.FramebufferTextureLayer(
point, att, name, level as gl::types::GLint,
layer as gl::types::GLint) },
None => unsafe { gl.FramebufferTexture(
point, att, name, level as gl::types::GLint) },
}
},
Command::BindUniformBlock(program, slot, loc, buffer) => {
let gl = &self.share.context;
unsafe {
gl.UniformBlockBinding(program, slot as gl::types::GLuint, loc as gl::types::GLuint);
gl.BindBufferBase(gl::UNIFORM_BUFFER, loc as gl::types::GLuint, buffer);
}
},
Command::BindUniform(loc, uniform) => {
let gl = &self.share.context;
shade::bind_uniform(gl, loc as gl::types::GLint, uniform);
},
Command::BindTexture(slot, kind, texture, sampler) => {
let gl = &self.share.context;
let anchor = tex::bind_texture(gl,
gl::TEXTURE0 + slot as gl::types::GLenum,
kind, texture);
match (anchor, kind.get_aa_mode(), sampler) {
(anchor, None, Some((name, info))) => {
if self.share.capabilities.sampler_objects_supported {
unsafe { gl.BindSampler(slot as gl::types::GLenum, name) };
} else {
debug_assert_eq!(name, 0);
tex::bind_sampler(gl, anchor, &info);
}
},
(_, Some(_), Some(_)) =>
error!("Unable to bind a multi-sampled texture with a sampler"),
(_, _, _) => (),
}
},
Command::SetDrawColorBuffers(num) => {
state::bind_draw_color_buffers(&self.share.context, num);
},
Command::SetPrimitiveState(prim) => {
state::bind_primitive(&self.share.context, prim);
},
Command::SetViewport(rect) => {
state::bind_viewport(&self.share.context, rect);
},
Command::SetMultiSampleState(ms) => {
state::bind_multi_sample(&self.share.context, ms);
},
Command::SetScissor(rect) => {
state::bind_scissor(&self.share.context, rect);
},
Command::SetDepthStencilState(depth, stencil, cull) => {
let gl = &self.share.context;
state::bind_stencil(gl, stencil, cull);
state::bind_depth(gl, depth);
},
Command::SetBlendState(blend) => {
state::bind_blend(&self.share.context, blend);
},
Command::SetColorMask(mask) => {
state::bind_color_mask(&self.share.context, mask);
},
Command::UpdateBuffer(buffer, pointer, offset) => {
let data = data_buf.get_ref(pointer);
factory::update_sub_buffer(&self.share.context, buffer,
data.as_ptr(), data.len(), offset, gfx::BufferRole::Vertex);
},
Command::UpdateTexture(kind, texture, image_info, pointer) => {
let data = data_buf.get_ref(pointer);
match tex::update_texture(&self.share.context, kind, texture,
&image_info, data.as_ptr(), data.len()) {
Ok(_) => (),
Err(e) => {
error!("Error updating a texture: {:?}", e);
},
}
},
Command::Draw(prim_type, start, count, instances) => {
let gl = &self.share.context;
match instances {
Some((num, base)) if self.share.capabilities.instance_call_supported => unsafe {
gl.DrawArraysInstancedBaseInstance(
primitive_to_gl(prim_type),
start as gl::types::GLsizei,
count as gl::types::GLsizei,
num as gl::types::GLsizei,
base as gl::types::GLuint,
);
},
Some(_) => {
error!("Instanced draw calls are not supported");
},
None => unsafe {
gl.DrawArrays(
primitive_to_gl(prim_type),
start as gl::types::GLsizei,
count as gl::types::GLsizei
);
},
}
},
Command::DrawIndexed(prim_type, index_type, start, count, base_vertex, instances) => {
let gl = &self.share.context;
let caps = &self.share.capabilities;
let (offset, gl_index) = match index_type {
IntSize::U8 => (start * 1u32, gl::UNSIGNED_BYTE),
IntSize::U16 => (start * 2u32, gl::UNSIGNED_SHORT),
IntSize::U32 => (start * 4u32, gl::UNSIGNED_INT),
};
match instances {
Some((num, base_instance)) if caps.instance_call_supported => unsafe {
if (base_vertex == 0 && base_instance == 0) || !caps.vertex_base_supported {
if base_vertex != 0 || base_instance != 0 {
error!("Instance bases with indexed drawing is not supported")
}
gl.DrawElementsInstanced(
primitive_to_gl(prim_type),
count as gl::types::GLsizei,
gl_index,
offset as *const gl::types::GLvoid,
num as gl::types::GLsizei,
);
} else if base_vertex != 0 && base_instance == 0 {
gl.DrawElementsInstancedBaseVertex(
primitive_to_gl(prim_type),
count as gl::types::GLsizei,
gl_index,
offset as *const gl::types::GLvoid,
num as gl::types::GLsizei,
base_vertex as gl::types::GLint,
);
} else if base_vertex == 0 && base_instance != 0 {
gl.DrawElementsInstancedBaseInstance(
primitive_to_gl(prim_type),
count as gl::types::GLsizei,
gl_index,
offset as *const gl::types::GLvoid,
num as gl::types::GLsizei,
base_instance as gl::types::GLuint,
);
} else {
gl.DrawElementsInstancedBaseVertexBaseInstance(
primitive_to_gl(prim_type),
count as gl::types::GLsizei,
gl_index,
offset as *const gl::types::GLvoid,
num as gl::types::GLsizei,
base_vertex as gl::types::GLint,
base_instance as gl::types::GLuint,
);
}
},
Some(_) => {
error!("Instanced draw calls are not supported");
},
None => unsafe {
if base_vertex == 0 || !caps.vertex_base_supported {
if base_vertex != 0 {
error!("Base vertex with indexed drawing not supported");
}
gl.DrawElements(
primitive_to_gl(prim_type),
count as gl::types::GLsizei,
gl_index,
offset as *const gl::types::GLvoid,
);
} else {
gl.DrawElementsBaseVertex(
primitive_to_gl(prim_type),
count as gl::types::GLsizei,
gl_index,
offset as *const gl::types::GLvoid,
base_vertex as gl::types::GLint,
);
}
},
}
},
Command::Blit(mut s_rect, d_rect, mirror, mask) => {
type GLint = gl::types::GLint;
let mut s_end_x = s_rect.x + s_rect.w;
let mut s_end_y = s_rect.y + s_rect.h;
if mirror.intersects(d::target::MIRROR_X) {
s_end_x = s_rect.x;
s_rect.x += s_rect.w;
}
if mirror.intersects(d::target::MIRROR_Y) {
s_end_y = s_rect.y;
s_rect.y += s_rect.h;
}
let mut flags = 0;
if mask.intersects(d::target::COLOR) {
flags |= gl::COLOR_BUFFER_BIT;
}
if mask.intersects(d::target::DEPTH) {
flags |= gl::DEPTH_BUFFER_BIT;
}
if mask.intersects(d::target::STENCIL) {
flags |= gl::STENCIL_BUFFER_BIT;
}
let filter = if s_rect.w == d_rect.w && s_rect.h == d_rect.h {
gl::NEAREST
}else {
gl::LINEAR
};
let gl = &self.share.context;
unsafe { gl.BlitFramebuffer(
s_rect.x as GLint,
s_rect.y as GLint,
s_end_x as GLint,
s_end_y as GLint,
d_rect.x as GLint,
d_rect.y as GLint,
(d_rect.x + d_rect.w) as GLint,
(d_rect.y + d_rect.h) as GLint,
flags,
filter
) };
},
}
self.check(cmd);
}
}
impl gfx::Device for Device {
type Resources = Resources;
type CommandBuffer = draw::CommandBuffer;
fn get_capabilities<'a>(&'a self) -> &'a d::Capabilities {
&self.share.capabilities
}
fn reset_state(&mut self) {
let data = d::draw::DataBuffer::new();
for com in RESET_CB.iter() {
self.process(com, &data);
}
}
fn submit(&mut self, (cb, db, handles): d::SubmitInfo<Device>) {
self.frame_handles.extend(handles);
self.reset_state();
for com in cb.iter() {
self.process(com, db);
}
match self.max_resource_count {
Some(c) if self.frame_handles.count() > c => {
error!("Way too many resources in the current frame. Did you call Device::after_frame()?");
self.max_resource_count = None;
},
_ => (),
}
}
fn cleanup(&mut self) {
use gfx::device::handle::Producer;
self.frame_handles.clear();
self.share.handles.borrow_mut().clean_with(&mut &self.share.context,
|gl, v| unsafe { gl.DeleteBuffers(1, v) },
|gl, v| unsafe { gl.DeleteVertexArrays(1, v) },
|gl, v| unsafe { gl.DeleteShader(*v) },
|gl, v| unsafe { gl.DeleteProgram(*v) },
|gl, v| unsafe { gl.DeleteFramebuffers(1, v) },
|gl, v| unsafe { gl.DeleteRenderbuffers(1, v) },
|gl, v| unsafe { gl.DeleteTextures(1, v) },
|gl, v| unsafe { gl.DeleteSamplers(1, v) },
);
}
}