// Copyright (C) 2015 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/gui/gui_gfx_renderer.hh" #include "nv/interface/device.hh" #include "nv/interface/context.hh" #include "nv/core/logging.hh" static const char *nv_gui_vertex_shader = R"( #version 120 attribute vec2 nv_position; attribute vec2 nv_texcoord; attribute vec4 nv_color; varying vec4 v_color; varying vec2 v_texcoord; uniform mat4 nv_m_projection; void main(void) { gl_Position = nv_m_projection * vec4(nv_position.x, nv_position.y, 0.0, 1.0); v_texcoord = nv_texcoord; v_color = nv_color; } )"; static const char *nv_gui_fragment_shader = R"( #version 120 varying vec4 v_color; varying vec2 v_texcoord; uniform sampler2D nv_t_diffuse; void main(void) { vec4 tex_color = texture2D( nv_t_diffuse, v_texcoord ); gl_FragColor = v_color * tex_color; } )"; using namespace nv; using namespace nv::gui; struct vertex { ivec2 position; vec2 texcoord; vec4 color; vertex() {} vertex( const nv::ivec2& cr, const nv::vec2& tc, const nv::vec4& c ) : position( cr ), texcoord( tc ), color( c ) { } }; const ivec2 atlas_size = ivec2( 1024, 1024 ); struct gui_quad { vertex vtx[6]; gui_quad( const nv::ivec2& coorda, const nv::ivec2& coordb, const nv::vec4& color ) { set_color( color ); vtx[0].position = coorda; vtx[1].position = nv::ivec2( coorda.x, coordb.y ); vtx[2].position = coordb; vtx[3].position = coordb; vtx[4].position = nv::ivec2( coordb.x, coorda.y ); vtx[5].position = coorda; } gui_quad( const nv::ivec2& coorda, const nv::ivec2& coordb, const nv::vec4& color, const nv::vec2& tcoorda, const nv::vec2& tcoordb ) { set_color( color ); vtx[0].position = coorda; vtx[1].position = nv::ivec2( coorda.x, coordb.y ); vtx[2].position = coordb; vtx[3].position = coordb; vtx[4].position = nv::ivec2( coordb.x, coorda.y ); vtx[5].position = coorda; vtx[0].texcoord = tcoorda; vtx[1].texcoord = nv::vec2( tcoorda.x, tcoordb.y ); vtx[2].texcoord = tcoordb; vtx[3].texcoord = tcoordb; vtx[4].texcoord = nv::vec2( tcoordb.x, tcoorda.y ); vtx[5].texcoord = tcoorda; } gui_quad( const nv::ivec2& coorda, const nv::ivec2& coordb, const nv::ivec2& coordc, const nv::ivec2& coordd, const nv::vec4& color ) { set_color( color ); vtx[0].position = coorda; vtx[1].position = coordb; vtx[2].position = coordc; vtx[3].position = coordc; vtx[4].position = coordd; vtx[5].position = coorda; } inline void set_color( const nv::vec4& color ) { vtx[0].color = color; vtx[1].color = color; vtx[2].color = color; vtx[3].color = color; vtx[4].color = color; vtx[5].color = color; } }; class screen_render_data : public render_data { public: screen_render_data( context* actx, nv::size_t initial_size ) : buffer( actx, VERTEX_BUFFER, DYNAMIC_DRAW, initial_size ), ctx( actx ), varray(), shader() { } ~screen_render_data() { ctx->get_device()->release( shader ); ctx->release( varray ); } nv::sliced_buffer buffer; nv::context* ctx; nv::texture tex; nv::vertex_array varray; nv::program shader; }; class element_render_data : public render_data { public: element_render_data( nv::sliced_buffer* cbuffer ) : buffer( cbuffer ) { } nv::buffer_slice< gui_quad > buffer; }; gfx_renderer::gfx_renderer( window* w ) : m_window( w ) , m_atlas( atlas_size, 4 ) , m_reupload( true ) { NV_LOG_TRACE( "Creating GUI renderer..." ); m_context = w->get_context(); m_area.dim( dimension( w->get_width(), w->get_height() ) ); region white = m_atlas.get_region( ivec2( 3, 3 ) ); size_t wsize = m_atlas.get_depth() * 4 * 4; uint8* wfill = new uint8[m_atlas.get_depth() * 4 * 4]; std::fill( wfill, wfill + wsize, 255 ); white.pos = ivec2(); m_atlas.set_region( white, wfill ); delete[] wfill; NV_LOG_TRACE( "Creating render data..." ); screen_render_data* sr = new screen_render_data( w->get_context(), 1024 ); m_render_data = sr; sr->shader = m_window->get_device()->create_program( nv_gui_vertex_shader, nv_gui_fragment_shader ); m_scene_state.get_camera().set_ortho( 0.0f, float( m_window->get_width() ), float( m_window->get_height() ), 0.0f ); sr->varray = m_window->get_context()->create_vertex_array(); buffer vb = sr->buffer.get_buffer(); m_window->get_context()->add_vertex_buffers< vertex >( sr->varray, vb ); nv::sampler sampler( nv::sampler::LINEAR, nv::sampler::CLAMP_TO_EDGE ); sr->tex = m_window->get_device()->create_texture( m_atlas.get_size(), image_format( nv::RGBA, nv::UBYTE ), sampler, nullptr ); m_render_state.depth_test.enabled = false; m_render_state.culling.enabled = false; m_render_state.blending.enabled = true; m_render_state.blending.src_rgb_factor = blending::SRC_ALPHA; m_render_state.blending.dst_rgb_factor = blending::ONE_MINUS_SRC_ALPHA; m_render_state.blending.src_alpha_factor = blending::SRC_ALPHA; m_render_state.blending.dst_alpha_factor = blending::ONE_MINUS_SRC_ALPHA; NV_LOG_TRACE( "GUI Renderer created" ); } texture_font* gfx_renderer::get_font( nv::size_t name ) const { if ( name >= m_fonts.size() ) return nullptr; return m_fonts[name]; } const image_info* gfx_renderer::get_image( nv::size_t name ) const { if ( name >= m_images.size() ) return nullptr; return &m_images[name]; } nv::size_t gfx_renderer::load_font( const string_view& filename, nv::size_t size ) { std::string id_name( filename.data(), filename.size() ); char buffer[8]; size_t len = nv::sint32_to_buffer( sint32( size ), buffer ); id_name.append( std::string( buffer, len ) ); string_view id( id_name.c_str(), id_name.size() ); auto i = m_font_names.find( id ); if ( i != m_font_names.end() ) { return i->second; } size_t result = m_fonts.size(); texture_font* f = new texture_font( &m_atlas, filename.data(), static_cast( size ) ); f->load_glyphs( "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ " ); m_fonts.push_back( f ); m_reupload = true; m_font_names[ id ] = result; return result; } nv::size_t gfx_renderer::load_image( const string_view& filename ) { auto i = m_image_names.find( filename ); if ( i != m_image_names.end() ) { return i->second; } size_t result = m_images.size(); image_data* data = m_window->get_device()->create_image_data( filename ); // TODO: Repitching assert( data->get_depth() == 4 ); region r = m_atlas.get_region( data->get_size() ); m_atlas.set_region( r, data->get_data() ); m_images.emplace_back( vec2( r.pos ) / vec2( atlas_size ), vec2( r.size + r.pos ) / vec2( atlas_size ), r.size ); delete data; m_reupload = true; m_image_names[filename] = result; return result; } void gfx_renderer::redraw( element* e, uint32 ) { screen_render_data* sr = reinterpret_cast< screen_render_data* >( m_render_data ); if ( e->m_render_data == nullptr ) { e->m_render_data = new element_render_data( &sr->buffer ); } element_render_data* er = reinterpret_cast< element_render_data* >( e->m_render_data ); size_t size_before = er->buffer.data().size(); std::vector< gui_quad >& qvec = er->buffer.lock(); qvec.clear(); rectangle abs = e->m_absolute; if ( e->m_absolute != m_area ) { int border = 0; vec4 color; std::string path; std::string text; const char* stext[] = { nullptr, "selected", "hover" }; const char* selector = stext[border]; if ( e->m_flags[HOVER] ) selector = stext[2]; if ( e->m_flags[SELECTED] ) selector = stext[1]; if ( m_style.get( e, "skin", selector, path ) ) { size_t image_id = load_image( string_view( path.c_str(), path.size() ) ); const image_info* image = get_image( image_id ); if ( image ) { color = vec4( 2, 2, 2, 1 ); ivec2 isize3 = image->size / 3; ivec2 isize3x = ivec2( isize3.x, 0 ); ivec2 isize3y = ivec2( 0, isize3.y ); vec2 tsize = ( image->t2 - image->t1 ); vec2 tsize3 = ( image->t2 - image->t1 ) / 3.0f; vec2 tsize32 = ( image->t2 - image->t1 ) * ( 2.0f / 3.0f ); vec2 tsizex = vec2( tsize.x, 0.0f ); //vec2 tsizey = vec2( 0.0f, tsize.y ); vec2 tsize3x = vec2( tsize3.x, 0.0f ); vec2 tsize3y = vec2( 0.0f, tsize3.y ); vec2 tsize3x2 = vec2( tsize32.x, 0.0f ); vec2 tsize3y2 = vec2( 0.0f, tsize32.y ); rectangle inner = abs.shrinked( isize3 ); qvec.emplace_back( abs.ul, inner.ul, color, image->t1, image->t1 + tsize3 ); qvec.emplace_back( abs.ul + isize3x, inner.ur(), color, image->t1 + tsize3x, image->t1 + tsize3x2 + tsize3y ); qvec.emplace_back( abs.ur() - isize3x, inner.ur() + isize3x, color, image->t1 + tsize3x2, image->t1 + tsizex + tsize3y ); qvec.emplace_back( abs.ul + isize3y, inner.ll(), color, image->t1 + tsize3y, image->t1 + tsize3y2 + tsize3x ); qvec.emplace_back( inner.ul, inner.lr, color, image->t1 + tsize3, image->t1 + tsize32 ); qvec.emplace_back( inner.ur(), inner.lr + isize3x, color, image->t1 + tsize3 + tsize3x, image->t1 + tsize32 + tsize3x ); qvec.emplace_back( abs.ll() - isize3y, inner.ll() + isize3y, color, image->t1 + tsize3y2, image->t1 + tsize3y2 + tsize3 ); qvec.emplace_back( inner.ll(), abs.lr - isize3x, color, image->t1 + tsize3y2 + tsize3x, image->t1 + tsize32 + tsize3y ); qvec.emplace_back( inner.lr, abs.lr, color, image->t1 + tsize32, image->t1 + tsize ); // qvec.emplace_back( abs.ul, abs.ll(), inner.ll(), inner.ul, color ); // qvec.emplace_back( inner.ur(), inner.lr, abs.lr, abs.ur(), color ); // qvec.emplace_back( inner.ll(), abs.ll(), abs.lr, inner.lr, color ); abs = inner; } } else { if ( m_style.get( e, "border", selector, border ) && m_style.get( e, "border_color", selector, color ) ) { rectangle inner = abs.shrinked( border ); qvec.emplace_back( abs.ul, inner.ul, inner.ur(), abs.ur(), color ); qvec.emplace_back( abs.ul, abs.ll(), inner.ll(), inner.ul, color ); qvec.emplace_back( inner.ur(), inner.lr, abs.lr, abs.ur(), color ); qvec.emplace_back( inner.ll(), abs.ll(), abs.lr, inner.lr, color ); abs = inner; } if ( m_style.get( e, "background_color", selector, color ) ) { qvec.emplace_back( abs.ul, abs.lr, color ); } } text = e->m_text; if ( !text.empty() ) { if ( m_style.get( e, "text_color", selector, color ) && m_style.get( e, "text_font", selector, path ) && m_style.get( e, "text_size", selector, border ) ) { size_t font_id = load_font( string_view( path.c_str(), path.size() ), size_t( border ) ); texture_font* font = get_font( font_id ); position p = abs.ul + position( 0, border ); for ( char c : text ) { const texture_glyph* g = font->get_glyph( static_cast( c ) ); if ( g ) { position gp = position( g->offset.x, -g->offset.y ); position p2 = p + g->size + gp; qvec.emplace_back( p + gp, p2, color, g->tl, g->br ); p += g->advance; } } } } } if ( size_before != er->buffer.data().size() ) { sr->buffer.reset(); } } void gfx_renderer::on_hover_change( element* e, bool /*hover*/ ) { // TODO: FIX int fix_me; NV_LOG_DEBUG( "on_hover_change" ); e->m_flags[DIRTY] = true; } void gfx_renderer::on_select_change( element* e, bool /*select*/ ) { // TODO: FIX int fix_me; NV_LOG_DEBUG( "on_select_change" ); e->m_flags[DIRTY] = true; } void gfx_renderer::draw( element* e ) { element_render_data* er = reinterpret_cast< element_render_data* >( e->m_render_data ); er->buffer.commit(); } void gfx_renderer::draw() { screen_render_data* sr = reinterpret_cast< screen_render_data* >( m_render_data ); if ( m_reupload ) { m_context->update( sr->tex, m_atlas.get_data() ); m_reupload = false; } if ( sr->buffer.commit() ) { buffer vb = sr->buffer.get_buffer(); m_context->replace_vertex_buffer( sr->varray, vb, false ); } m_context->bind( sr->tex, TEX_DIFFUSE ); m_context->draw( TRIANGLES, m_render_state, m_scene_state, sr->shader, sr->varray, sr->buffer.get_size() * 6 ); } gfx_renderer::~gfx_renderer() { for ( auto p : m_fonts ) { delete p; } if ( m_render_data ) { m_context->get_device()->release( reinterpret_cast< screen_render_data* >( m_render_data )->tex ); delete m_render_data; } }