// Copyright (C) 2012-2014 ChaosForge Ltd // This file is part of NV Libraries. // For conditions of distribution and use, see copyright notice in nv.hh #include "nv/gl/gl_context.hh" #include "nv/gl/gl_enum.hh" #include "nv/lib/gl.hh" #include "nv/lib/sdl.hh" #include "nv/gl/gl_device.hh" using namespace nv; nv::vertex_array nv::gl_context::create_vertex_array() { vertex_array result = m_vertex_arrays.create(); vertex_array_info* info = m_vertex_arrays.get( result ); info->count = 0; info->index = buffer(); info->index_owner = false; info->index_type = USHORT; return result; } nv::framebuffer nv::gl_context::create_framebuffer() { if ( is_gl_extension_loaded( GL_EXT_FRAMEBUFFER_OBJECT ) ) { unsigned glid = 0; glGenFramebuffers( 1, &glid ); framebuffer result = m_framebuffers.create(); gl_framebuffer_info* info = m_framebuffers.get( result ); info->glid = glid; info->color_attachment_count = 0; return result; } else return framebuffer(); } void nv::gl_context::release( vertex_array va ) { vertex_array_info* info = m_vertex_arrays.get( va ); if ( info ) { for ( uint32 i = 0; i < info->count; ++i ) { if ( info->attr[i].owner ) m_device->release( info->attr[i].vbuffer ); } if ( info->index.is_valid() && info->index_owner) m_device->release( info->index ); m_vertex_arrays.destroy( va ); } } void nv::gl_context::release( framebuffer f ) { gl_framebuffer_info* info = m_framebuffers.get( f ); if ( info ) { // TODO: release textures? glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &info->glid); } } const vertex_array_info* nv::gl_context::get_vertex_array_info( vertex_array va ) const { return m_vertex_arrays.get( va ); } const framebuffer_info* nv::gl_context::get_framebuffer_info( framebuffer f ) const { return m_framebuffers.get( f ); } vertex_array_info* nv::gl_context::get_vertex_array_info_mutable( vertex_array va ) { return m_vertex_arrays.get( va ); } void gl_context::bind( texture t, texture_slot slot ) { const gl_texture_info* info = static_cast< const gl_texture_info* >( m_device->get_texture_info( t ) ); if ( info ) { glActiveTexture( GL_TEXTURE0 + static_cast< GLenum >( slot ) ); glBindTexture( GL_TEXTURE_2D, info->glid ); } } void nv::gl_context::bind( program p ) { gl_program_info* info = ((gl_device*)m_device)->m_programs.get( p ); if ( info ) { glUseProgram( info->glid ); ((gl_device*)m_device)->update_uniforms( info ); } } // 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 ) { const vertex_array_info* info = m_vertex_arrays.get( va ); if ( info ) { 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< const gl_buffer_info* >( 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 ), GL_FALSE, static_cast( vba.stride ), (void*)vba.offset ); } glBindBuffer( GL_ARRAY_BUFFER, 0 ); if ( info->index.is_valid() ) { const gl_buffer_info* iinfo = static_cast< const gl_buffer_info* >( m_device->get_buffer_info( info->index ) ); if ( iinfo && iinfo->type == INDEX_BUFFER ) { glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, iinfo->glid ); } else { // TODO: report error } } } } void nv::gl_context::bind( framebuffer f ) { const gl_framebuffer_info* info = m_framebuffers.get( f ); if ( info ) { glBindFramebuffer( GL_FRAMEBUFFER, info->glid ); } } void nv::gl_context::unbind( program ) { glUseProgram( 0 ); } // 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 ) { 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 ) ); } } } 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 ); } } void nv::gl_context::update( texture t, void* data ) { const gl_texture_info* info = static_cast< const gl_texture_info* >( m_device->get_texture_info( t ) ); if ( info ) { image_format format = info->format; ivec2 size = info->size; glBindTexture( GL_TEXTURE_2D, info->glid ); glTexImage2D( GL_TEXTURE_2D, 0, (GLint)nv::image_format_to_enum(format.format), size.x, size.y, 0, nv::image_format_to_enum(format.format), nv::datatype_to_gl_enum(format.type), data ); } } void gl_context::update( buffer b, const void* data, size_t offset, size_t 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 ( viewport.z < 0 || viewport.w < 0 ) { NV_THROW( logic_error, "viewport width and height 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.dim.x < 0 || scissor.dim.y < 0 ) { NV_THROW( logic_error, "scissor_test.rect width and height must be greater than zero!" ); } if ( scissor.enabled ) { 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_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 ) { if ( range.near < 0.0 || range.near > 1.0 ) { NV_THROW( logic_error, "render_state.depth_range.near must be between zero and one!"); } if ( range.far < 0.0 || range.far > 1.0 ) { NV_THROW( logic_error, "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_polygon_mode( state.polygon_mode ); } gl_context::gl_context( device* a_device ) : context( a_device ) { } nv::gl_context::~gl_context() { while ( m_framebuffers.size() > 0 ) release( m_framebuffers.get_handle(0) ); while ( m_vertex_arrays.size() > 0 ) release( m_vertex_arrays.get_handle(0) ); } void nv::gl_context::apply_engine_uniforms( program p, const scene_state& s ) { gl_program_info* info = ((gl_device*)m_device)->m_programs.get( p ); if ( info ) { for ( auto u : info->m_engine_uniforms ) { u->set( this, &s ); } } } void gl_context::draw( primitive prim, const render_state& rs, program p, vertex_array va, size_t count ) { 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 ), 0 ); } else { glDrawArrays( primitive_to_enum(prim), 0, static_cast( count ) ); } unbind( va ); //unbind( p ); } } nv::sdl_gl_context::sdl_gl_context( device* a_device, void* a_sdl_win_handle ) : gl_context( a_device ), m_handle( nullptr ) { #if NV_SDL_VERSION == NV_SDL_20 m_handle = SDL_GL_CreateContext( static_cast( a_sdl_win_handle ) ); if ( m_handle == 0 ) { NV_LOG( LOG_CRITICAL, "GL Context creation failed: " << SDL_GetError( ) ); return; // TODO: Error report } #else NV_UNUSED( a_win_handle ); NV_UNUSED( m_handle ); #endif nv::load_gl_library(); NV_LOG( LOG_INFO, "OpenGL Vendor : " << glGetString(GL_VENDOR) ); NV_LOG( LOG_INFO, "OpenGL Renderer : " << glGetString(GL_RENDERER) ); NV_LOG( LOG_INFO, "OpenGL Version : " << glGetString(GL_VERSION) ); NV_LOG( LOG_INFO, "OpenGL GLSL Version : " << glGetString(GL_SHADING_LANGUAGE_VERSION) ); #if NV_SDL_VERSION == NV_SDL_20 // SDL_GL_SetSwapInterval(1); #endif // TODO: do we really need this? glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); force_apply_render_state( m_render_state ); } nv::sdl_gl_context::~sdl_gl_context() { #if NV_SDL_VERSION == NV_SDL_20 SDL_GL_DeleteContext(static_cast( m_handle ) ); #endif } nv::native_gl_context::native_gl_context( device* a_device, void* a_native_win_handle ) : gl_context( a_device ), m_handle( nullptr ) { #if NV_PLATFORM == NV_WINDOWS // TODO: error checking HDC hdc = (HDC)a_native_win_handle; const int wgl_attrib_list[] = { WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, WGL_SUPPORT_OPENGL_ARB, GL_TRUE, WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, WGL_DOUBLE_BUFFER_ARB, GL_TRUE, WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, WGL_COLOR_BITS_ARB, 32, WGL_DEPTH_BITS_ARB, 24, WGL_STENCIL_BITS_ARB, 8, 0, 0 //End }; unsigned int num_formats; int pixel_format; PIXELFORMATDESCRIPTOR pfd; if ( FALSE == wglChoosePixelFormatARB(hdc, wgl_attrib_list, NULL, 1, &pixel_format, &num_formats) ) { return; } if ( FALSE == SetPixelFormat(hdc, pixel_format, &pfd) ) { //int err = GetLastError(); return; } int attribs[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, 2, WGL_CONTEXT_MINOR_VERSION_ARB, 1, WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, //WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, 0, 0 //End }; HGLRC handle; if ( 0 == (handle = wglCreateContextAttribsARB(hdc, 0, attribs) ) ) { return; } if ( FALSE == dynwglMakeCurrent( hdc, handle ) ) { return; } load_gl_library( NV_GL_PATH, true ); load_wgl_library( NV_GL_PATH, true ); m_handle = (void*)handle; #else NV_ASSERT( false, "Native GL context not implemented for this platform!" ); #endif force_apply_render_state( m_render_state ); } nv::native_gl_context::~native_gl_context() { #if NV_PLATFORM == NV_WINDOWS dynwglDeleteContext( (HGLRC)m_handle ); #endif }