// Copyright (C) 2012-2017 ChaosForge Ltd // http://chaosforge.org/ // // This file is part of Nova libraries. // For conditions of distribution and use, see copying.txt file in root folder. #include "nv/gl/gl_context.hh" #include "nv/gl/gl_enum.hh" #include "nv/lib/gl.hh" #include "nv/gl/gl_device.hh" #include "nv/core/logger.hh" using namespace nv; nv::vertex_array nv::gl_context::create_vertex_array( const vertex_array_desc& desc ) { vertex_array result = m_vertex_arrays.create(); gl_vertex_array_info* info = m_vertex_arrays.get( result ); glGenVertexArrays( 1, &info->glid ); info->count = desc.count; info->index = desc.index; info->index_owner = desc.index_owner; info->index_type = desc.index_type; for ( uint32 i = 0; i < desc.count; ++i ) info->attr[i] = desc.attr[i]; glBindVertexArray( info->glid ); for ( uint32 i = 0; i < info->count; ++i ) { const vertex_buffer_attribute& vba = info->attr[i]; uint32 location = static_cast( vba.location ); glEnableVertexAttribArray( location ); const gl_buffer_info* vinfo = static_cast( m_device->get_buffer_info( vba.vbuffer ) ); if ( vinfo && vinfo->type == VERTEX_BUFFER ) glBindBuffer( GL_ARRAY_BUFFER, vinfo->glid ); else { // TODO: report error } glVertexAttribPointer( location, static_cast( vba.components ), nv::datatype_to_gl_enum( vba.dtype ), vba.interpolate ? GL_TRUE : GL_FALSE, static_cast( vba.stride ), reinterpret_cast( vba.offset ) ); } glBindBuffer( GL_ARRAY_BUFFER, 0 ); if ( info->index.is_valid() ) { const gl_buffer_info* iinfo = static_cast( m_device->get_buffer_info( info->index ) ); if ( iinfo && iinfo->type == INDEX_BUFFER ) { glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, iinfo->glid ); } else { // TODO: report error } } glBindVertexArray( 0 ); if ( info->index.is_valid() ) { glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 ); } // for ( uint32 i = 0; i < info->count; ++i ) // { // glDisableVertexAttribArray( static_cast( info->attr[i].location ) ); // } return result; } nv::framebuffer nv::gl_context::create_framebuffer( uint32 temp_samples ) { unsigned glid = 0; glGenFramebuffers( 1, &glid ); framebuffer result = m_framebuffers.create(); gl_framebuffer_info* info = m_framebuffers.get( result ); info->glid = glid; info->depth_rb_glid = 0; info->color_attachment_count = 0; info->sample_count = temp_samples; return result; } void nv::gl_context::release( vertex_array va ) { if ( gl_vertex_array_info* info = m_vertex_arrays.get( va ) ) { release( info ); m_vertex_arrays.destroy( va ); } } void nv::gl_context::release( gl_vertex_array_info* info ) { if ( info ) { for ( uint32 i = 0; i < info->count; ++i ) { if ( info->attr[i].owner ) release( info->attr[i].vbuffer ); } if ( info->index.is_valid() && info->index_owner ) release( info->index ); glDeleteVertexArrays( 1, &info->glid ); } } void nv::gl_context::release( framebuffer f ) { if ( gl_framebuffer_info* info = m_framebuffers.get( f ) ) { release( info ); m_framebuffers.destroy( f ); } } void nv::gl_context::release( gl_framebuffer_info* info ) { if ( info ) { // TODO: release textures? glBindFramebuffer( GL_FRAMEBUFFER, 0 ); glBindRenderbuffer( GL_RENDERBUFFER, 0 ); if ( info->depth_rb_glid == 0 ) glDeleteRenderbuffers( 1, &info->depth_rb_glid ); glDeleteFramebuffers( 1, &info->glid ); } } nv::image_data* nv::gl_context::dump_image( pixel_format format, image_data* reuse ) { NV_ASSERT_ALWAYS( format == nv::RGB8 || format == nv::RGBA8, "Bad format passed to dump" ); glPixelStorei( GL_PACK_ALIGNMENT, 1 ); image_data* result = reuse; datatype type = get_pixel_format_info( format ).type; if ( !result ) result = new image_data( format, ivec2( m_viewport.z, m_viewport.w ) ); glReadPixels( 0, 0, m_viewport.z, m_viewport.w, format == nv::RGB8 ? GL_RGB : GL_RGBA, datatype_to_gl_enum( type ), const_cast< uint8* >( result->get_data() ) ); return result; } const framebuffer_info* nv::gl_context::get_framebuffer_info( framebuffer f ) const { return m_framebuffers.get( f ); } void nv::gl_context::attach( framebuffer f, output_slot slot, texture t, int layer /* = -1*/ ) { // TODO: framebuffer variable, so no re-binding? // TODO: support 1d, 3d textures, cubemaps or layers? // TODO: support GL_READ_FRAMEBUFFER? const gl_framebuffer_info* info = m_framebuffers.get( f ); const gl_texture_info* tinfo = static_cast< const gl_texture_info* >( m_device->get_texture_info( t ) ); if ( info ) { glBindFramebuffer( GL_FRAMEBUFFER, info->glid ); unsigned gl_type = texture_type_to_enum( tinfo->type ); unsigned tglid = tinfo ? tinfo->glid : 0; if ( layer >= 0 ) { glFramebufferTextureLayer( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + unsigned( slot ), tglid, 0, layer ); } else { glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + unsigned( slot ), gl_type, tglid, 0 ); } } } void nv::gl_context::attach( framebuffer f, texture depth, int layer /*= -1*/ ) { // TODO: framebuffer variable, so no re-binding? // TODO: support GL_READ_FRAMEBUFFER? const gl_framebuffer_info* info = m_framebuffers.get( f ); const gl_texture_info* tinfo = static_cast< const gl_texture_info* >( m_device->get_texture_info( depth ) ); if ( info ) { glBindFramebuffer( GL_FRAMEBUFFER, info->glid ); unsigned gl_type = texture_type_to_enum( tinfo->type ); unsigned glid = ( tinfo ? tinfo->glid : 0 ); if ( layer >= 0 ) { glFramebufferTextureLayer( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, glid, 0, layer ); } else { glFramebufferTexture2D( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, gl_type, glid, 0 ); } } } void nv::gl_context::attach( framebuffer f, ivec2 size ) { // TODO: framebuffer variable, so no re-binding? // TODO: support GL_READ_FRAMEBUFFER? gl_framebuffer_info* info = m_framebuffers.get( f ); if ( info ) { glBindFramebuffer( GL_FRAMEBUFFER, info->glid ); if ( info->depth_rb_glid ) glDeleteRenderbuffers( 1, &(info->depth_rb_glid) ); glGenRenderbuffers( 1, &(info->depth_rb_glid) ); glBindRenderbuffer( GL_RENDERBUFFER, info->depth_rb_glid ); if ( info->sample_count > 1 ) glRenderbufferStorageMultisample( GL_RENDERBUFFER, info->sample_count, GL_DEPTH_COMPONENT16, size.x, size.y ); else glRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, size.x, size.y ); glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, info->depth_rb_glid ); glBindRenderbuffer( GL_RENDERBUFFER, 0 ); } } void nv::gl_context::attach_depth( framebuffer source, framebuffer target ) { gl_framebuffer_info* sinfo = m_framebuffers.get( source ); gl_framebuffer_info* tinfo = m_framebuffers.get( target ); if ( sinfo && tinfo ) { glBindFramebuffer( GL_FRAMEBUFFER, tinfo->glid ); // TODO: do we need to bind the renderbuffer at all? glBindRenderbuffer( GL_RENDERBUFFER, sinfo->depth_rb_glid ); glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, sinfo->depth_rb_glid ); glBindRenderbuffer( GL_RENDERBUFFER, 0 ); glBindRenderbuffer( GL_FRAMEBUFFER, 0 ); } } void nv::gl_context::blit( framebuffer f, buffer_mask mask, ivec2 src1, ivec2 src2, ivec2 dst1, ivec2 dst2 ) { gl_framebuffer_info* info = m_framebuffers.get( f ); if ( info ) { glBindFramebuffer( GL_FRAMEBUFFER, info->glid ); unsigned filter = mask == buffer_mask::COLOR_BUFFER ? GL_LINEAR : GL_NEAREST; glBlitFramebuffer( src1.x, src1.y, src2.x, src2.y, dst1.x, dst1.y, dst2.x, dst2.y, clear_state_buffers_to_mask( mask ), filter ); } } void nv::gl_context::blit( framebuffer from, framebuffer to, buffer_mask mask, ivec2 src1, ivec2 src2, ivec2 dst1, ivec2 dst2 ) { gl_framebuffer_info* finfo = m_framebuffers.get( from ); gl_framebuffer_info* tinfo = m_framebuffers.get( to ); if ( finfo ) { glBindFramebuffer( GL_READ_FRAMEBUFFER, finfo->glid ); glBindFramebuffer( GL_DRAW_FRAMEBUFFER, tinfo ? tinfo->glid : 0 ); unsigned filter = mask == buffer_mask::COLOR_BUFFER ? GL_LINEAR : GL_NEAREST; glBlitFramebuffer( src1.x, src1.y, src2.x, src2.y, dst1.x, dst1.y, dst2.x, dst2.y, clear_state_buffers_to_mask( mask ), filter ); glBindFramebuffer( GL_READ_FRAMEBUFFER, 0 ); if ( tinfo ) glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 ); } } bool nv::gl_context::check( framebuffer_slot ft ) { glDrawBuffer( 0 ); glReadBuffer( 0 ); unsigned result = glCheckFramebufferStatus( framebuffer_slot_to_enum(ft) ); if ( result == GL_FRAMEBUFFER_COMPLETE ) return true; switch ( result ) { case GL_FRAMEBUFFER_UNDEFINED : NV_LOG_ERROR( "gl_context::check : Framebuffer undefined!" ); break; case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE : NV_LOG_ERROR( "gl_context::check : Incomplete multisample!" ); break; case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS : NV_LOG_ERROR( "gl_context::check : Incomplete layer targets!" ); break; case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT : NV_LOG_ERROR( "gl_context::check : Framebuffer incomplete attachment!" ); break; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT : NV_LOG_ERROR( "gl_context::check : Framebuffer missing attachment!" ); break; // case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS : NV_LOG_ERROR( "gl_context::check : Framebuffer incomplete dimensions!" ); break; // case GL_FRAMEBUFFER_INCOMPLETE_FORMATS : NV_LOG_ERROR( "gl_context::check : Framebuffer incomplete formats!" ); break; case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER : NV_LOG_ERROR( "gl_context::check : Framebuffer incomplete draw buffer!" ); break; case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER : NV_LOG_ERROR( "gl_context::check : Framebuffer incomplete read buffer!" ); break; case GL_FRAMEBUFFER_UNSUPPORTED : NV_LOG_ERROR( "gl_context::check : Framebuffer format combination unsupported!" ); break; default: NV_LOG_ERROR( "gl_context::check : Unknown Framebuffer error! (", result, ")" ); break; } glDrawBuffer( GL_BACK ); glReadBuffer( GL_BACK ); return false; } void nv::gl_context::bind( framebuffer f, framebuffer_slot ft /*= FRAMEBUFFER_BOTH */ ) { const gl_framebuffer_info* info = m_framebuffers.get( f ); if ( info ) { glBindFramebuffer( framebuffer_slot_to_enum(ft), info->glid ); } else { glBindFramebuffer( framebuffer_slot_to_enum(ft), 0 ); } } void nv::gl_context::bind( buffer b, texture t ) { const gl_buffer_info* binfo = static_cast< const gl_buffer_info* >( m_device->get_buffer_info( b ) ); const gl_texture_info* tinfo = static_cast< const gl_texture_info* >( m_device->get_texture_info( t ) ); NV_ASSERT( binfo && tinfo, "Bad buffer or texture passed to create_texture" ); if ( binfo && tinfo ) { NV_ASSERT_ALWAYS( binfo->type == TEXTURE_BUFFER && tinfo->type == TEXTURE_1D_BUFFER, "bad texture or buffer type!" ); bind( t, TEXTURE_0 ); glTexBuffer( GL_TEXTURE_BUFFER, image_format_to_internal_enum( tinfo->format ), binfo->glid ); } } void nv::gl_context::bind( buffer b, uint32 index, uint32 offset /*= 0*/, uint32 size /*= 0 */ ) { const gl_buffer_info* info = static_cast< const gl_buffer_info* >( m_device->get_buffer_info( b ) ); if ( info ) { if ( size == 0 ) glBindBufferBase( buffer_type_to_enum( info->type ), index, info->glid ); else glBindBufferRange( buffer_type_to_enum( info->type ), index, info->glid, static_cast( offset ), static_cast( size ) ); } } void gl_context::bind( texture t, texture_slot slot ) { if ( m_bound_textures[ slot ] != t ) { const gl_texture_info* info = static_cast< const gl_texture_info* >( m_device->get_texture_info( t ) ); if ( info ) { set_active_texture( slot ); glBindTexture( texture_type_to_enum( info->type ), info->glid ); m_bound_textures[slot] = t; } } } void nv::gl_context::bind( program p ) { gl_device* gdevice = static_cast( m_device ); gl_program_info* info = gdevice->m_programs.get( p ); if ( info ) { if ( m_bound_program != p ) glUseProgram( info->glid ); gdevice->update_uniforms( info ); if ( !info->validated ) { validate_program( p ); } m_bound_program = p; } } bool nv::gl_context::validate_program( program p ) { gl_device* gdevice = static_cast( m_device ); gl_program_info* info = gdevice->m_programs.get( p ); if ( info ) { info->validated = true; const uint32 buffer_size = 1024; char buffer[buffer_size] = { 0 }; int length; int status; glValidateProgram( info->glid ); glGetProgramiv( info->glid, GL_VALIDATE_STATUS, &status ); if ( status == GL_FALSE ) { glGetProgramInfoLog( info->glid, buffer_size, &length, buffer ); NV_LOG_ERROR( "Program #", info->glid, " validation error : ", buffer ); return false; } return true; } return false; } // void nv::gl_context::bind( buffer b ) // { // const gl_buffer_info* info = static_cast< const gl_buffer_info* >( m_device->get_buffer_info( b ) ); // if ( info ) // { // glBindBuffer( buffer_type_to_enum( info->type ), info->glid ); // } // } void nv::gl_context::bind( vertex_array va ) { gl_vertex_array_info* info = m_vertex_arrays.get( va ); if ( info ) { glBindVertexArray( info->glid ); } } void nv::gl_context::unbind( program ) { if ( m_bound_program ) glUseProgram( 0 ); m_bound_program = program(); } void nv::gl_context::set_active_texture( texture_slot slot ) { if ( slot != m_active_slot ) { glActiveTexture( GL_TEXTURE0 + static_cast( slot ) ); m_active_slot = slot; } } // void nv::gl_context::unbind( buffer b ) // { // const gl_buffer_info* info = static_cast< const gl_buffer_info* >( m_device->get_buffer_info( b ) ); // if ( info ) // { // glBindBuffer( buffer_type_to_enum( info->type ), 0 ); // } // } void nv::gl_context::unbind( vertex_array va ) { const vertex_array_info* info = m_vertex_arrays.get( va ); if ( info ) { glBindVertexArray( 0 ); } } void nv::gl_context::unbind( framebuffer f ) { // this way we are sure that the extension is loaded const gl_framebuffer_info* info = m_framebuffers.get( f ); if ( info ) { glBindFramebuffer( GL_FRAMEBUFFER, 0 ); } glDrawBuffer( GL_BACK ); glReadBuffer( GL_BACK ); } void nv::gl_context::update( texture t, const void* data ) { const gl_texture_info* info = static_cast< const gl_texture_info* >( m_device->get_texture_info( t ) ); NV_ASSERT_ALWAYS( info->type != TEXTURE_1D_BUFFER, "Buffer texture passed to update!" ); NV_ASSERT_ALWAYS( info->type != TEXTURE_2D_MULTISAMPLE, "Multisample texture passed to update!" ); if ( info ) { pixel_format format = info->format; datatype type = get_pixel_format_info( format ).type; ivec3 size = info->size; unsigned gl_type = texture_type_to_enum( info->type ); bind( t, texture_slot::TEXTURE_0 ); if ( info->type == TEXTURE_3D || info->type == TEXTURE_2D_ARRAY ) // glTexImage3D( gl_type, 0, static_cast( nv::image_format_to_internal_enum( format ) ), size.x, size.y, size.z, 0, nv::image_format_to_enum( format ), nv::datatype_to_gl_enum( type ), data ); glTexSubImage3D( gl_type, 0, 0, 0, 0, size.x, size.y, size.z, nv::image_format_to_enum( format ), nv::datatype_to_gl_enum( type ), data ); else // glTexImage2D( gl_type, 0, static_cast( nv::image_format_to_internal_enum(format) ), size.x, size.y, 0, nv::image_format_to_enum(format), nv::datatype_to_gl_enum(type), data ); glTexSubImage2D( gl_type, 0, 0, 0, size.x, size.y, nv::image_format_to_enum( format ), nv::datatype_to_gl_enum( type ), data ); } } void* nv::gl_context::map_buffer( buffer b, buffer_access ba, uint32 offset, uint32 length ) { const gl_buffer_info* info = static_cast( m_device->get_buffer_info( b ) ); unsigned int glenum = buffer_type_to_enum( info->type ); glBindBuffer( glenum, info->glid ); void* result = glMapBufferRange( glenum, GLintptr( offset ), GLsizeiptr( length ), buffer_access_to_bitfield( ba ) ); if ( result == nullptr ) { while ( GLenum err = glGetError() ) switch ( err ) { case GL_INVALID_VALUE : NV_LOG_ERROR( "map_buffer failed : GL_INVALID_VALUE" ) break; case GL_INVALID_OPERATION : NV_LOG_ERROR( "map_buffer failed : GL_INVALID_OPERATION " ) break; case GL_OUT_OF_MEMORY : NV_LOG_ERROR( "map_buffer failed : GL_OUT_OF_MEMORY " ) break; default: break; } } return result; } void nv::gl_context::unmap_buffer( buffer b ) { const gl_buffer_info* info = static_cast( m_device->get_buffer_info( b ) ); glUnmapBuffer( buffer_type_to_enum( info->type ) ); } // void nv::gl_context::update( buffer b, uint32 index, const void* data, uint32 offset, uint32 size ) // { // const gl_buffer_info* info = static_cast( m_device->get_buffer_info( b ) ); // if ( info ) // { // GLenum glenum = buffer_type_to_enum( info->type ); // if ( size == 0 ) // glBindBufferBase( glenum, index, info->glid ); // else // glBindBufferRange( glenum, index, info->glid, offset, size ); // glBufferSubData( glenum, GLintptr( offset ), GLsizeiptr( size ), data ); // } // } void gl_context::update( buffer b, const void* data, nv::uint32 offset, nv::uint32 size ) { const gl_buffer_info* info = static_cast< const gl_buffer_info* >( m_device->get_buffer_info( b ) ); if ( info ) { GLenum glenum = buffer_type_to_enum( info->type ); glBindBuffer( glenum, info->glid ); glBufferSubData( glenum, GLintptr( offset ), GLsizeiptr( size ), data ); } } void gl_context::clear( const clear_state& cs ) { // apply_framebuffer apply_scissor_test( cs.scissor_test ); apply_color_mask( cs.color_mask ); apply_depth_mask( cs.depth_mask ); // stencil_mask_separate if ( m_clear_color != cs.color ) { glClearColor( cs.color.r, cs.color.g, cs.color.b, cs.color.a ); m_clear_color = cs.color; } if ( m_clear_depth != cs.depth ) { glClearDepth( cs.depth ); m_clear_depth = cs.depth; } if ( m_clear_stencil != cs.stencil ) { glClearStencil( cs.stencil ); m_clear_stencil = cs.stencil; } glClear( clear_state_buffers_to_mask( cs.buffers ) ); } const ivec4& gl_context::get_viewport() { return m_viewport; } void gl_context::set_viewport( const ivec4& viewport ) { if ( m_viewport != viewport ) { NV_ASSERT_ALWAYS( viewport.z > 0 && viewport.w > 0, "viewport dimensions must be greater than zero!" ); m_viewport = viewport; glViewport( viewport.x, viewport.y, viewport.z, viewport.w ); } } void gl_context::enable( unsigned int what, bool value ) { if ( value ) { glEnable( what ); } else { glDisable( what ); } } void gl_context::apply_stencil_test( const stencil_test& stencil ) { if ( m_render_state.stencil_test.enabled != stencil.enabled ) { enable( GL_STENCIL_TEST, stencil.enabled ); m_render_state.stencil_test.enabled = stencil.enabled; } if ( stencil.enabled ) { apply_stencil_face( GL_FRONT, m_render_state.stencil_test.front_face, stencil.front_face ); apply_stencil_face( GL_BACK, m_render_state.stencil_test.back_face, stencil.back_face ); } } void gl_context::apply_stencil_face( unsigned face, stencil_test_face& stencil, const stencil_test_face& new_stencil ) { if (( stencil.op_fail != new_stencil.op_fail ) || ( stencil.op_depth_fail != new_stencil.op_depth_fail ) || ( stencil.op_depth_pass != new_stencil.op_depth_pass ) ) { glStencilOpSeparate( face, stencil_operation_to_enum( new_stencil.op_fail ), stencil_operation_to_enum( new_stencil.op_depth_fail ), stencil_operation_to_enum( new_stencil.op_depth_pass ) ); stencil.op_fail = new_stencil.op_fail; stencil.op_depth_fail = new_stencil.op_depth_fail; stencil.op_depth_pass = new_stencil.op_depth_pass; } if (( stencil.function != new_stencil.function ) || ( stencil.ref_value != new_stencil.ref_value ) || ( stencil.mask != new_stencil.mask )) { glStencilFuncSeparate( face, stencil_function_to_enum( new_stencil.function ), new_stencil.ref_value, new_stencil.mask ); stencil.function = new_stencil.function; stencil.ref_value = new_stencil.ref_value; stencil.mask = new_stencil.mask; } } void gl_context::apply_scissor_test( const scissor_test& scissor ) { if ( m_render_state.scissor_test.enabled != scissor.enabled ) { enable( GL_SCISSOR_TEST, scissor.enabled ); m_render_state.scissor_test.enabled = scissor.enabled; } if ( scissor.enabled ) { NV_ASSERT_ALWAYS( scissor.dim.x > 0 && scissor.dim.y > 0, "scissor_test.rect dimension equal to zero!" ); if ( m_render_state.scissor_test.dim != scissor.dim || m_render_state.scissor_test.pos != scissor.pos ) { glScissor( scissor.pos.x, scissor.pos.y, scissor.dim.x, scissor.dim.y ); m_render_state.scissor_test.dim = scissor.dim; m_render_state.scissor_test.pos = scissor.pos; } } } void gl_context::apply_depth_test( const depth_test& depth ) { if ( m_render_state.depth_test.enabled != depth.enabled ) { enable( GL_DEPTH_TEST, depth.enabled ); m_render_state.depth_test.enabled = depth.enabled; } if ( depth.enabled ) { if ( m_render_state.depth_test.function != depth.function ) { glDepthFunc( depth_state_function_to_enum( depth.function ) ); m_render_state.depth_test.function = depth.function; } } } void gl_context::apply_depth_mask( bool mask ) { if ( m_render_state.depth_mask != mask ) { glDepthMask( mask ); m_render_state.depth_mask = mask; } } void gl_context::apply_multisample( bool multisample ) { if ( m_render_state.multisample != multisample ) { enable( GL_MULTISAMPLE, multisample ); m_render_state.multisample = multisample; } } void gl_context::apply_polygon_mode( const polygon_mode& mode ) { if ( m_render_state.polygon_mode.fill != mode.fill ) { glPolygonMode( GL_FRONT_AND_BACK, polygon_mode_fill_to_enum( mode.fill ) ); m_render_state.polygon_mode.fill = mode.fill; } } void gl_context::apply_depth_range( const depth_range& range ) { NV_ASSERT_ALWAYS( range.near >= 0.0 && range.near <= 1.0, "render_state.depth_range.near must be between zero and one!" ); NV_ASSERT_ALWAYS( range.far >= 0.0 && range.far <= 1.0, "render_state.depth_range.far must be between zero and one!" ); if ((m_render_state.depth_range.far != range.far) || (m_render_state.depth_range.near != range.near)) { glDepthRange( range.near, range.far ); m_render_state.depth_range.far = range.far; m_render_state.depth_range.near = range.near; } } void gl_context::apply_color_mask( const color_mask& mask ) { if ( m_render_state.color_mask != mask ) { glColorMask( mask.red, mask.green, mask.blue, mask.alpha ); m_render_state.color_mask = mask; } } void gl_context::apply_blending( const blending& blend ) { if ( m_render_state.blending.enabled != blend.enabled ) { enable( GL_BLEND, blend.enabled ); m_render_state.blending.enabled = blend.enabled; } if ( blend.enabled ) { if ((m_render_state.blending.src_rgb_factor != blend.src_rgb_factor ) || (m_render_state.blending.dst_rgb_factor != blend.dst_rgb_factor ) || (m_render_state.blending.src_alpha_factor != blend.src_alpha_factor ) || (m_render_state.blending.dst_alpha_factor != blend.dst_alpha_factor )) { glBlendFuncSeparate( blending_factor_to_enum( blend.src_rgb_factor ), blending_factor_to_enum( blend.dst_rgb_factor ), blending_factor_to_enum( blend.src_alpha_factor ), blending_factor_to_enum( blend.dst_alpha_factor ) ); m_render_state.blending.src_rgb_factor = blend.src_rgb_factor; m_render_state.blending.dst_rgb_factor = blend.dst_rgb_factor; m_render_state.blending.src_alpha_factor = blend.src_alpha_factor; m_render_state.blending.dst_alpha_factor = blend.dst_alpha_factor; } if ((m_render_state.blending.rgb_equation != blend.rgb_equation ) || (m_render_state.blending.alpha_equation != blend.alpha_equation )) { glBlendEquationSeparate( blending_equation_to_enum( blend.rgb_equation ), blending_equation_to_enum( blend.alpha_equation ) ); m_render_state.blending.rgb_equation = blend.rgb_equation; m_render_state.blending.alpha_equation = blend.alpha_equation; } if (( m_render_state.blending.color != blend.color )) { glBlendColor( blend.color.r, blend.color.g, blend.color.b, blend.color.a ); m_render_state.blending.color = blend.color; } } } void gl_context::apply_culling( const culling& cull ) { if ( m_render_state.culling.enabled != cull.enabled ) { enable( GL_CULL_FACE, cull.enabled ); m_render_state.culling.enabled = cull.enabled; } if ( cull.enabled ) { if ( m_render_state.culling.face != cull.face ) { glCullFace( culling_face_type_to_enum( cull.face ) ); m_render_state.culling.face = cull.face; } } if ( m_render_state.culling.order != cull.order ) { glFrontFace( culling_order_type_to_enum( cull.order ) ); m_render_state.culling.order = cull.order; } } void gl_context::force_apply_render_state( const render_state& state ) { enable( GL_CULL_FACE, state.culling.enabled ); glCullFace( culling_face_type_to_enum( state.culling.face ) ); glFrontFace( culling_order_type_to_enum( state.culling.order ) ); enable( GL_SCISSOR_TEST, state.scissor_test.enabled ); glScissor( state.scissor_test.pos.x, state.scissor_test.pos.y, state.scissor_test.dim.x, state.scissor_test.dim.y ); enable( GL_STENCIL_TEST, state.stencil_test.enabled ); force_apply_stencil_face( GL_FRONT, state.stencil_test.front_face ); force_apply_stencil_face( GL_BACK, state.stencil_test.back_face ); enable( GL_DEPTH_TEST, state.depth_test.enabled ); glDepthFunc( depth_state_function_to_enum( state.depth_test.function ) ); glDepthRange( state.depth_range.near, state.depth_range.far ); enable( GL_BLEND, state.blending.enabled ); glBlendFuncSeparate( blending_factor_to_enum( state.blending.src_rgb_factor ), blending_factor_to_enum( state.blending.dst_rgb_factor ), blending_factor_to_enum( state.blending.src_alpha_factor ), blending_factor_to_enum( state.blending.dst_alpha_factor ) ); glBlendEquationSeparate( blending_equation_to_enum( state.blending.rgb_equation ), blending_equation_to_enum( state.blending.alpha_equation ) ); glBlendColor( state.blending.color.r, state.blending.color.g, state.blending.color.b, state.blending.color.a ); glDepthMask( state.depth_mask ); glColorMask( state.color_mask.red, state.color_mask.green, state.color_mask.blue, state.color_mask.alpha ); glPolygonMode( GL_FRONT_AND_BACK, polygon_mode_fill_to_enum( state.polygon_mode.fill ) ); } void gl_context::force_apply_stencil_face( unsigned face, const stencil_test_face& stencil ) { glStencilOpSeparate( face, stencil_operation_to_enum( stencil.op_fail ), stencil_operation_to_enum( stencil.op_depth_fail ), stencil_operation_to_enum( stencil.op_depth_pass ) ); glStencilFuncSeparate( face, stencil_function_to_enum( stencil.function ), stencil.ref_value, stencil.mask ); } void gl_context::apply_render_state( const render_state& state ) { // apply_primitive_restart apply_culling( state.culling ); // apply_program_point_size // apply_rasterization_mode apply_scissor_test( state.scissor_test ); apply_stencil_test( state.stencil_test ); apply_depth_test( state.depth_test ); apply_depth_range( state.depth_range ); apply_blending( state.blending ); apply_color_mask( state.color_mask ); apply_depth_mask( state.depth_mask ); apply_multisample( state.multisample ); apply_polygon_mode( state.polygon_mode ); } gl_context::gl_context( device* a_device, void* a_handle ) : context( a_device ), m_handle( a_handle ) { // TODO: configurable: // load_gl_extensions( GL_EXT_FRAMEBUFFER_BLIT | GL_EXT_FRAMEBUFFER_OBJECT | GL_EXT_TEXTURE_ARRAY ); m_active_slot = texture_slot( -1 ); force_apply_render_state( m_render_state ); glHint( GL_POLYGON_SMOOTH_HINT, GL_NICEST ); } nv::gl_context::~gl_context() { for ( auto& info : m_framebuffers ) release( &info ); for ( auto& info : m_vertex_arrays ) release( &info ); } void nv::gl_context::apply_engine_uniforms( program p, const scene_state& s ) { gl_program_info* info = static_cast( m_device )->m_programs.get( p ); if ( info ) { for ( auto& u : *info->m_engine_uniforms ) { u->set( this, &s ); } } } nv::texture nv::gl_context::create_texture( texture_type type, ivec2 size, pixel_format aformat, sampler asampler, const void* data /*= nullptr */ ) { texture result = create_texture( type, aformat ); gl_texture_info* info = static_cast( m_device )->get_full_texture_info( result ); bind( result, texture_slot::TEXTURE_0 ); unsigned glid = info->glid; unsigned gl_type = texture_type_to_enum( type ); GLenum gl_internal = GLenum( image_format_to_internal_enum( aformat ) ); unsigned gl_enum = image_format_to_enum( aformat ); bool is_depth = aformat == DEPTH16 || aformat == DEPTH24 || aformat == DEPTH32; // Detect if mipmapping was requested // if ( gl_type == GL_TEXTURE_2D && gl_enum != GL_RED_INTEGER && asampler.filter_min != sampler::LINEAR && asampler.filter_min != sampler::NEAREST ) // { // // TODO: This should not be done if we use framebuffers! // glTexParameteri( gl_type, GL_GENERATE_MIPMAP, GL_TRUE); // } if ( asampler.filter_max != sampler::NEAREST ) { asampler.filter_max = sampler::LINEAR; } if ( gl_type != GL_TEXTURE_2D_MULTISAMPLE ) { glTexParameteri( gl_type, GL_TEXTURE_MIN_FILTER, GLint( nv::sampler_filter_to_enum( asampler.filter_min ) ) ); glTexParameteri( gl_type, GL_TEXTURE_MAG_FILTER, GLint( nv::sampler_filter_to_enum( asampler.filter_max ) ) ); } if ( gl_type != GL_TEXTURE_2D_MULTISAMPLE && gl_enum != GL_RED_INTEGER ) { glTexParameteri( gl_type, GL_TEXTURE_WRAP_S, GLint( nv::sampler_wrap_to_enum( asampler.wrap_s ) ) ); glTexParameteri( gl_type, GL_TEXTURE_WRAP_T, GLint( nv::sampler_wrap_to_enum( asampler.wrap_t ) ) ); } if ( is_depth ) { // glTexParameteri( gl_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); // glTexParameteri( gl_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); // This is to allow usage of shadow2DProj function in the shader glTexParameteri( gl_type, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE ); glTexParameteri( gl_type, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL ); } // #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE // #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF // // float aniso = 0.0f; // glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &aniso ); // NV_LOG_INFO( "Anisotropy at ", aniso, " (", int( aniso ), " ) " ); // glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso ); if ( gl_type != GL_TEXTURE_2D_MULTISAMPLE ) glTexImage2D( gl_type, 0, gl_internal, size.x, size.y, 0, gl_enum, nv::datatype_to_gl_enum( get_pixel_format_info( aformat ).type ), data ); else glTexImage2DMultisample( gl_type, 4, gl_internal, size.x, size.y, 1 ); if ( gl_type == GL_TEXTURE_2D && gl_enum != GL_RED_INTEGER && asampler.filter_min != sampler::LINEAR && asampler.filter_min != sampler::NEAREST ) { // TODO: This should not be done if we use framebuffers! glGenerateMipmap( gl_type ); } bind( texture(), texture_slot::TEXTURE_0 ); info->type = type; info->format = aformat; info->tsampler = asampler; info->size = ivec3( size.x, size.y, 1 ); info->glid = glid; return result; } nv::texture nv::gl_context::create_texture( texture_type type, ivec3 size, pixel_format aformat, sampler asampler, const void* data /*= nullptr */ ) { texture result = create_texture( type, aformat ); gl_texture_info* info = static_cast( m_device )->get_full_texture_info( result ); bind( result, texture_slot::TEXTURE_0 ); unsigned glid = info->glid; unsigned gl_type = texture_type_to_enum( type ); bool is_depth = aformat == DEPTH16 || aformat == DEPTH24 || aformat == DEPTH32; if ( asampler.filter_max != sampler::NEAREST ) { asampler.filter_max = sampler::LINEAR; } glTexParameteri( gl_type, GL_TEXTURE_MIN_FILTER, GLint( nv::sampler_filter_to_enum( asampler.filter_min ) ) ); glTexParameteri( gl_type, GL_TEXTURE_MAG_FILTER, GLint( nv::sampler_filter_to_enum( asampler.filter_max ) ) ); glTexParameteri( gl_type, GL_TEXTURE_WRAP_S, GLint( nv::sampler_wrap_to_enum( asampler.wrap_s ) ) ); glTexParameteri( gl_type, GL_TEXTURE_WRAP_T, GLint( nv::sampler_wrap_to_enum( asampler.wrap_t ) ) ); glTexParameteri( gl_type, GL_TEXTURE_WRAP_R, GLint( nv::sampler_wrap_to_enum( asampler.wrap_r ) ) ); if ( is_depth ) { glTexParameteri( gl_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri( gl_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); // This is to allow usage of shadow2DProj function in the shader glTexParameteri( gl_type, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE ); glTexParameteri( gl_type, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL ); } //glTexStorage3D( GL_TEXTURE_2D_ARRAY, mipLevelCount, GL_RGBA8, width, height, layerCount ); glTexImage3D( gl_type, 0, GLint( nv::image_format_to_internal_enum( aformat ) ), size.x, size.y, size.z, 0, nv::image_format_to_enum( aformat ), nv::datatype_to_gl_enum( get_pixel_format_info( aformat ).type ), data ); bind( texture(), texture_slot::TEXTURE_0 ); info->type = type; info->format = aformat; info->tsampler = asampler; info->size = size; info->glid = glid; return result; } nv::buffer nv::gl_context::create_buffer( buffer_type type, buffer_hint hint, uint32 size, const void* source /*= nullptr */ ) { buffer result = static_cast( m_device )->create_buffer( type, hint ); if ( size > 0 ) create_buffer( result, size, source ); return result; } void nv::gl_context::create_buffer( buffer b, uint32 size, const void* source /*= nullptr */ ) { gl_buffer_info* info = static_cast( m_device )->get_full_buffer_info( b ); if ( info ) { unsigned glenum = buffer_type_to_enum( info->type ); glBindBuffer( glenum, info->glid ); glBufferData( glenum, GLsizeiptr( size ), source, buffer_hint_to_enum( info->hint ) ); glBindBuffer( glenum, 0 ); } } void nv::gl_context::set_draw_buffers( uint32 count, const output_slot* slots ) { if ( count == 0 ) return; if ( count == 1 ) { set_draw_buffer( slots[0] ); return; } unsigned int buffers[8]; count = nv::min( count, 8 ); for ( uint32 i = 0; i < count; ++i ) { buffers[i] = output_slot_to_enum( slots[i] ); if ( slots[i] > OUTPUT_7 ) buffers[i] = 0; } glDrawBuffers( GLsizei( count ), buffers ); } void nv::gl_context::set_draw_buffer( output_slot slot ) { glDrawBuffer( output_slot_to_enum(slot) ); } void nv::gl_context::set_read_buffer( output_slot slot ) { glReadBuffer( output_slot_to_enum(slot) ); } void gl_context::draw( primitive prim, const render_state& rs, program p, vertex_array va, nv::uint32 count, nv::uint32 first ) { apply_render_state( rs ); const vertex_array_info* info = m_vertex_arrays.get( va ); if ( count > 0 && info ) { bind( p ); bind( va ); if ( info->index.is_valid() ) { glDrawElements( primitive_to_enum(prim), static_cast( count ), datatype_to_gl_enum( info->index_type ), reinterpret_cast< const void* >( size_t( get_datatype_info( info->index_type ).size * first ) ) ); } else { glDrawArrays( primitive_to_enum(prim), static_cast( first ), static_cast( count ) ); } unbind( va ); //unbind( p ); } } void nv::gl_context::draw_instanced( primitive prim, const render_state& rs, program p, uint32 instances, vertex_array va, uint32 count, uint32 first /*= 0 */ ) { apply_render_state( rs ); const vertex_array_info* info = m_vertex_arrays.get( va ); if ( count > 0 && info ) { bind( p ); bind( va ); if ( info->index.is_valid() ) { glDrawElementsInstanced( primitive_to_enum( prim ), static_cast( count ), datatype_to_gl_enum( info->index_type ), reinterpret_cast( size_t( get_datatype_info( info->index_type ).size * first ) ), instances ); } else { glDrawArraysInstanced( primitive_to_enum( prim ), static_cast( first ), static_cast( count ), instances ); } unbind( va ); //unbind( p ); } }