// Copyright (C) 2012-2013 ChaosForge / Kornel Kisielewicz // http://chaosforge.org/ // // This file is part of NV Libraries. // For conditions of distribution and use, see copyright notice in nv.hh #include "nv/gui/gui_renderer.hh" #include #include "nv/interface/device.hh" #include "nv/interface/context.hh" using namespace nv; using namespace nv::gui; 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].coord = coorda; vtx[1].coord = nv::ivec2( coorda.x, coordb.y ); vtx[2].coord = coordb; vtx[3].coord = coordb; vtx[4].coord = nv::ivec2( coordb.x, coorda.y ); vtx[5].coord = 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].coord = coorda; vtx[1].coord = nv::ivec2( coorda.x, coordb.y ); vtx[2].coord = coordb; vtx[3].coord = coordb; vtx[4].coord = nv::ivec2( coordb.x, coorda.y ); vtx[5].coord = coorda; vtx[0].tcoord = tcoorda; vtx[1].tcoord = nv::vec2( tcoorda.x, tcoordb.y ); vtx[2].tcoord = tcoordb; vtx[3].tcoord = tcoordb; vtx[4].tcoord = nv::vec2( tcoordb.x, tcoorda.y ); vtx[5].tcoord = 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].coord = coorda; vtx[1].coord = coordb; vtx[2].coord = coordc; vtx[3].coord = coordc; vtx[4].coord = coordd; vtx[5].coord = 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( device* dev, size_t initial_size ) : buffer( dev, nv::DYNAMIC_DRAW, initial_size ), varray( nullptr ), shader(nullptr), texture(nullptr) { } ~screen_render_data() { delete shader; delete varray; delete texture; } nv::sliced_buffer buffer; nv::vertex_array* varray; nv::program* shader; nv::texture2d* texture; }; class element_render_data : public render_data { public: element_render_data( nv::sliced_buffer* cbuffer ) : buffer( cbuffer ) {} nv::buffer_slice< gui_quad > buffer; }; renderer::renderer( window* w, const std::string& shader_path ) : m_window(w) , m_style() , m_atlas( glm::ivec2( 1024, 1024 ), 4 ) , m_reupload( true ) { 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; screen_render_data* sr = new screen_render_data( w->get_device(), 1024 ); m_render_data = sr; // ** EXTREMELY TEMPORARY! sr->varray = m_window->get_device()->create_vertex_array(); sr->shader = m_window->get_device()->create_program( nv::slurp( shader_path + ".vert" ), nv::slurp( shader_path + ".frag" ) ); m_scene_state.get_camera().set_ortho( 0.0f, float( m_window->get_width() ), float( m_window->get_height() ), 0.0f ); vertex_buffer* vb = (vertex_buffer*)sr->buffer.get_buffer(); sr->varray->add_vertex_buffer( slot::POSITION, vb, nv::INT, 2, 0, sizeof( vertex ), false ); sr->varray->add_vertex_buffer( slot::TEXCOORD, vb, nv::FLOAT, 2, offset_of( &vertex::tcoord ), sizeof( vertex ), false ); sr->varray->add_vertex_buffer( slot::COLOR, vb, nv::FLOAT, 4, offset_of( &vertex::color ), sizeof( vertex ), false ); nv::sampler sampler( nv::sampler::LINEAR, nv::sampler::CLAMP_TO_EDGE ); sr->texture = m_window->get_device()->create_texture2d( m_atlas.get_size(), 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; } texture_font* renderer::get_font( size_t name ) const { if ( name >= m_fonts.size() ) return nullptr; return m_fonts[ name ]; } nv::vec4 renderer::get_image( size_t name ) const { if ( name >= m_images.size() ) return nv::vec4(); return m_images[ name ]; } size_t renderer::load_font( const std::string& filename, size_t size ) { std::string id_name( filename ); id_name.append( to_string( size ) ); auto i = m_font_names.find( id_name ); if ( i != m_font_names.end() ) { return i->second; } size_t result = (size_t)m_fonts.size(); texture_font* f = new texture_font( &m_atlas, filename.c_str(), (float)size ); f->load_glyphs( "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ " ); m_fonts.push_back( f ); m_reupload = true; m_font_names[ id_name ] = result; return result; } size_t renderer::load_image( const std::string& 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() ); delete data; m_reupload = true; m_image_names[ filename ] = result; return result; } void renderer::load_style( const std::string& filename ) { m_style.load_style( filename ); } void renderer::redraw( element* e, uint32 ) { screen_render_data* sr = (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 = (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; vec4 color; std::string path; std::string text; if ( m_style.get( e, "border", border ) && m_style.get( e, "border_color", 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", color ) ) { qvec.emplace_back( abs.ul, abs.lr, color ); } text = e->m_text; if ( !text.empty() ) { if ( m_style.get( e, "text_color", color ) && m_style.get( e, "text_font", path ) && m_style.get( e, "text_size", border ) ) { size_t font_id = load_font( path, (uint16)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 renderer::draw( element* e ) { element_render_data* er = (element_render_data*)(e->m_render_data); er->buffer.commit(); } void renderer::draw() { screen_render_data* sr = (screen_render_data*)m_render_data; if ( m_reupload ) { sr->texture->assign( (void*)m_atlas.get_data() ); m_reupload = false; } if ( sr->buffer.commit() ) { nv::vertex_buffer* vb = (nv::vertex_buffer*)sr->buffer.get_buffer(); sr->varray->update_vertex_buffer( nv::slot::POSITION, vb, false ); sr->varray->update_vertex_buffer( nv::slot::TEXCOORD, vb, false ); sr->varray->update_vertex_buffer( nv::slot::COLOR, vb, false ); } sr->texture->bind( nv::TEX_DIFFUSE ); m_window->get_context()->draw( TRIANGLES, m_render_state, m_scene_state, sr->shader, sr->varray, sr->buffer.get_size() * 6 ); } renderer::~renderer() { for ( auto p : m_fonts ) { delete p; } delete m_render_data; }