From ae3cecfa42a3abd7f9f7da583625201e4749f795 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Thu, 31 Oct 2019 19:37:53 +0100 Subject: [PATCH] opengl 3.3 renderer integration, minor renderer tweaks --- .gitignore | 4 +- Globals.cpp | 5 +- Globals.h | 2 + Model3d.cpp | 104 +++++--- Model3d.h | 2 + PyInt.cpp | 2 +- Texture.cpp | 342 +++++++++++++++++++++---- Texture.h | 5 +- Track.cpp | 14 +- application.cpp | 31 ++- drivermode.cpp | 3 +- drivermouseinput.cpp | 4 +- driveruipanels.cpp | 9 +- editormode.cpp | 2 +- geometrybank.cpp | 6 +- maszyna.vcxproj | 454 ++++++++++++++++++++++++++++++++++ maszyna.vcxproj.filters | 18 +- material.cpp | 9 + material.h | 4 +- opengl33geometrybank.cpp | 138 +++++++++++ opengl33geometrybank.h | 72 ++++++ opengl33particles.cpp | 6 +- opengl33renderer.cpp | 211 +++++++++------- opengl33renderer.h | 126 ++++++---- opengl33skydome.cpp | 69 ++++++ opengl33skydome.h | 36 +++ openglgeometrybank.cpp | 79 +++--- openglgeometrybank.h | 2 + openglprecipitation.cpp | 2 + openglprecipitation.h | 2 +- openglrenderer.cpp | 59 +++-- openglrenderer.h | 10 +- openglskydome.cpp | 10 +- ref/imgui/imgui.h | 4 + ref/imgui/imgui_widgets.cpp | 88 +++++++ renderer.h | 10 +- simulationenvironment.cpp | 2 + simulationenvironment.h | 7 + simulationstateserializer.cpp | 2 +- skydome.cpp | 10 + skydome.h | 2 +- stdafx.h | 2 + sun.cpp | 2 +- sun.h | 2 +- uilayer.cpp | 110 +++----- uiwidgets.cpp | 104 ++++++++ uiwidgets.h | 17 ++ utilities.cpp | 10 + utilities.h | 2 + 49 files changed, 1804 insertions(+), 412 deletions(-) create mode 100644 maszyna.vcxproj create mode 100644 opengl33geometrybank.cpp create mode 100644 opengl33geometrybank.h create mode 100644 opengl33skydome.cpp create mode 100644 opengl33skydome.h create mode 100644 uiwidgets.cpp create mode 100644 uiwidgets.h diff --git a/.gitignore b/.gitignore index 549b13c4..b7f64fc4 100644 --- a/.gitignore +++ b/.gitignore @@ -55,8 +55,8 @@ install_manifest.txt *.opensdf *.sdf *.sln -*.vcxproj -*.filters +#*.vcxproj +#*.filters format_all_files.py *.suo EU07.tds diff --git a/Globals.cpp b/Globals.cpp index 6dac0e27..fc4d0694 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -338,10 +338,9 @@ global_settings::ConfigParse(cParser &Parser) { } else if( token == "gfxrenderer" ) { // shadow render toggle - std::string gfxrenderer; Parser.getTokens(); - Parser >> gfxrenderer; - BasicRenderer = ( gfxrenderer == "simple" ); + Parser >> GfxRenderer; + BasicRenderer = ( GfxRenderer == "simple" ); } else if( token == "shadows" ) { // shadow render toggle diff --git a/Globals.h b/Globals.h index 75a89b6e..e3b92986 100644 --- a/Globals.h +++ b/Globals.h @@ -49,6 +49,7 @@ struct global_settings { std::string asCurrentSceneryPath{ "scenery/" }; std::string asCurrentTexturePath{ szTexturePath }; std::string asCurrentDynamicPath; + bool GfxFramebufferSRGB { true }; // settings // filesystem bool bLoadTraction{ true }; @@ -107,6 +108,7 @@ struct global_settings { float BaseDrawRange{ 2500.f }; int DynamicLightCount{ 3 }; bool ScaleSpecularValues{ true }; + std::string GfxRenderer{ "default" }; bool BasicRenderer{ false }; bool RenderShadows{ true }; int RenderCabShadowsRange{ 0 }; diff --git a/Model3d.cpp b/Model3d.cpp index fcfa5dc6..8a77c117 100644 --- a/Model3d.cpp +++ b/Model3d.cpp @@ -90,6 +90,30 @@ TSubModel::SetDiffuseOverride( glm::vec3 const &Color, bool const Includechildre } } +std::optional +TSubModel::GetDiffuse(float Includesiblings) { + if (eType == TP_FREESPOTLIGHT) { + if (DiffuseOverride.x >= 0.0f) + return DiffuseOverride; + else + return glm::vec3(f4Diffuse); + } + + if (Includesiblings) { + auto sibling = this; + while ((sibling = sibling->Next)) { + auto result = sibling->GetDiffuse(true); + if (result) + return result; + } + } + + if (Child) + return Child->GetDiffuse(true); + + return std::nullopt; +} + // sets visibility level (alpha component) to specified value void TSubModel::SetVisibilityLevel( float const Level, bool const Includechildren, bool const Includesiblings ) { @@ -335,7 +359,8 @@ int TSubModel::Load( cParser &parser, TModel3d *Model, /*int Pos,*/ bool dynamic std::string material = parser.getToken(); if (material == "none") { // rysowanie podanym kolorem - m_material = null_handle; + Name_Material( "colored" ); + m_material = GfxRenderer->Fetch_Material( m_materialname ); iFlags |= 0x10; // rysowane w cyklu nieprzezroczystych } else if (material.find("replacableskin") != material.npos) @@ -374,11 +399,18 @@ int TSubModel::Load( cParser &parser, TModel3d *Model, /*int Pos,*/ bool dynamic m_material = GfxRenderer->Fetch_Material( material ); // renderowanie w cyklu przezroczystych tylko jeśli: // 1. Opacity=0 (przejściowo <1, czy tam <100) oraz - iFlags |= ( - Opacity < 0.999f ? + // 2. tekstura ma przezroczystość + iFlags |= ( + ( ( Opacity < 0.01f ) + && ( ( m_material != null_handle ) + && ( GfxRenderer->Material( m_material ).is_translucent() ) ) ) ? 0x20 : - 0x10 ); // 0x20-przezroczysta, 0x10-nieprzezroczysta - }; + 0x10 ); // 0x10-nieprzezroczysta, 0x20-przezroczysta + }; + } + else if( eType == TP_STARS ) { + m_material = GfxRenderer->Fetch_Material( "stars" ); + iFlags |= 0x10; } else { iFlags |= 0x10; @@ -387,17 +419,26 @@ int TSubModel::Load( cParser &parser, TModel3d *Model, /*int Pos,*/ bool dynamic if (m_material > 0) { opengl_material const &mat = GfxRenderer->Material(m_material); - - // if material have opacity set, replace submodel opacity with it - if (!std::isnan(mat.opacity)) - { + /* + if( eType == TP_FREESPOTLIGHT ) { iFlags &= ~0x30; - if (mat.opacity == 0.0f) - iFlags |= 0x20; // translucent - else - iFlags |= 0x10; // opaque + iFlags |= 0x20; + } + else + */ + { + // if material has opacity set, replace submodel opacity with it + auto const opacity { ( + false == std::isnan( mat.opacity ) ? + mat.opacity : + Opacity ) }; + iFlags &= ~0x30; + iFlags |= ( + ( ( opacity < 0.01f ) + && ( GfxRenderer->Material( m_material ).is_translucent() ) ) ? + 0x20 : + 0x10 ); // 0x10-nieprzezroczysta, 0x20-przezroczysta } - // and same thing with selfillum if (!std::isnan(mat.selfillum)) fLight = mat.selfillum; @@ -1802,25 +1843,25 @@ void TSubModel::BinInit(TSubModel *s, float4x4 *m, std::vector *t, } */ m_material = GfxRenderer->Fetch_Material( m_materialname ); - // if we don't have phase flags set for some reason, try to fix it if (!(iFlags & 0x30) && m_material != null_handle) { - opengl_material const &mat = GfxRenderer->Material(m_material); - float opacity = mat.opacity; - - // if material don't have opacity set, try to guess it - if (std::isnan(opacity)) - opacity = mat.get_or_guess_opacity(); - - // set phase flag based on material opacity - if (opacity == 0.0f) - iFlags |= 0x20; // translucent + /* + if( eType == TP_FREESPOTLIGHT ) { + iFlags &= ~0x30; + iFlags |= 0x20; + } else - iFlags |= 0x10; // opaque + */ + { + // texture-alpha based fallback if for some reason we don't have opacity flag set yet + iFlags |= ( + GfxRenderer->Material( m_material ).is_translucent() ? + 0x20 : + 0x10 ); // 0x10-nieprzezroczysta, 0x20-przezroczysta + } } - - if (m_material > 0) + if ( m_material != null_handle ) { opengl_material const &mat = GfxRenderer->Material(m_material); @@ -1835,7 +1876,12 @@ void TSubModel::BinInit(TSubModel *s, float4x4 *m, std::vector *t, } } else - m_material = iTexture; + { + if( iTexture == 0 ) + m_material = GfxRenderer->Fetch_Material( "colored" ); + else + m_material = iTexture; + } b_aAnim = b_Anim; // skopiowanie animacji do drugiego cyklu diff --git a/Model3d.h b/Model3d.h index 43936838..d4a8ecea 100644 --- a/Model3d.h +++ b/Model3d.h @@ -208,6 +208,8 @@ public: void ColorsSet( glm::vec3 const &Ambient, glm::vec3 const &Diffuse, glm::vec3 const &Specular ); // sets rgb components of diffuse color override to specified value void SetDiffuseOverride( glm::vec3 const &Color, bool const Includechildren = false, bool const Includesiblings = false ); + // gets rgb components of any freespot diffuse color (searches also in children) + std::optional GetDiffuse( float Includesiblings = false ); // sets visibility level (alpha component) to specified value void SetVisibilityLevel( float const Level, bool const Includechildren = false, bool const Includesiblings = false ); // sets light level (alpha component of illumination color) to specified value diff --git a/PyInt.cpp b/PyInt.cpp index 78eb4f61..52f3e1a3 100644 --- a/PyInt.cpp +++ b/PyInt.cpp @@ -47,7 +47,7 @@ void render_task::run() { // build texture ::glTexImage2D( GL_TEXTURE_2D, 0, - GL_RGBA8, + ( Global.GfxFramebufferSRGB ? GL_SRGB8 : GL_RGBA8 ), PyInt_AsLong( outputwidth ), PyInt_AsLong( outputheight ), 0, GL_RGB, GL_UNSIGNED_BYTE, reinterpret_cast( PyString_AsString( output ) ) ); ::glFlush(); diff --git a/Texture.cpp b/Texture.cpp index 55da13ab..34d33c34 100644 --- a/Texture.cpp +++ b/Texture.cpp @@ -31,12 +31,201 @@ http://mozilla.org/MPL/2.0/. std::array opengl_texture::units = { 0 }; GLint opengl_texture::m_activeunit = -1; +std::unordered_map opengl_texture::precompressed_formats = +{ + { GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, 8 }, + { GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, 16 }, + { GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, 16 }, + { GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, 8 }, + { GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 16 }, + { GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, 16 }, +}; + +std::unordered_map opengl_texture::drivercompressed_formats = +{ + { GL_SRGB8_ALPHA8, GL_COMPRESSED_SRGB_ALPHA }, + { GL_SRGB8, GL_COMPRESSED_SRGB }, + { GL_RGBA8, GL_COMPRESSED_RGBA }, + { GL_RGB8, GL_COMPRESSED_RGB }, + { GL_RG8, GL_COMPRESSED_RG }, + { GL_R8, GL_COMPRESSED_RED }, +}; + +std::unordered_map> opengl_texture::mapping = +{ + // image have, material wants, gl internalformat + { GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, { { GL_SRGB_ALPHA, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT }, + { GL_SRGB, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT }, + { GL_RGBA, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT }, + { GL_RGB, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT }, + { GL_RG, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT }, + { GL_RED, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT } } }, + { GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, { { GL_SRGB_ALPHA, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT }, + { GL_SRGB, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT }, + { GL_RGBA, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT }, + { GL_RGB, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT }, + { GL_RG, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT }, + { GL_RED, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT } } }, + { GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, { { GL_SRGB_ALPHA, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT }, + { GL_SRGB, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT }, + { GL_RGBA, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT }, + { GL_RGB, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT }, + { GL_RG, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT }, + { GL_RED, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT } } }, + { GL_RGBA, { { GL_SRGB_ALPHA, GL_SRGB8_ALPHA8 }, + { GL_SRGB, GL_SRGB8 }, + { GL_RGBA, GL_RGBA8 }, + { GL_RGB, GL_RGB8 }, + { GL_RG, GL_RG8 }, + { GL_RED, GL_R8 } } }, + { GL_RGB, { { GL_SRGB_ALPHA, GL_SRGB8 }, // bad + { GL_SRGB, GL_SRGB8 }, + { GL_RGBA, GL_RGB8 }, // bad + { GL_RGB, GL_RGB8 }, + { GL_RG, GL_RG8 }, + { GL_RED, GL_R8 } } }, + { GL_RG, { { GL_SRGB_ALPHA, GL_SRGB8 }, // bad + { GL_SRGB, GL_SRGB8 }, // bad + { GL_RGBA, GL_RG8 }, // bad + { GL_RGB, GL_RG8 }, // bad + { GL_RG, GL_RG8 }, + { GL_RED, GL_R8 } } }, + { GL_RED, { { GL_SRGB_ALPHA, GL_SRGB8 }, // bad + { GL_SRGB, GL_SRGB8 }, // bad + { GL_RGBA, GL_R8 }, // bad + { GL_RGB, GL_R8 }, // bad + { GL_RG, GL_R8 }, // bad + { GL_RED, GL_R8 } } }, +}; + texture_manager::texture_manager() { // since index 0 is used to indicate no texture, we put a blank entry in the first texture slot m_textures.emplace_back( new opengl_texture(), std::chrono::steady_clock::time_point() ); } +// convert image to format suitable for given internalformat +// required for GLES, on desktop GL it will be done by driver +void opengl_texture::gles_match_internalformat(GLuint internalformat) +{ + // don't care about sRGB here + if (internalformat == GL_SRGB8) + internalformat = GL_RGB8; + if (internalformat == GL_SRGB8_ALPHA8) + internalformat = GL_RGBA8; + if (internalformat == GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT) + internalformat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + if (internalformat == GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT) + internalformat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + if (internalformat == GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT) + internalformat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + + // ignore compressed formats (and hope that GLES driver will support it) + if (precompressed_formats.find(internalformat) != precompressed_formats.end()) + return; + + // we don't want BGR(A), reverse it + if (data_format == GL_BGR) + { + std::vector reverse; + reverse.resize(data.size()); + + for (int y = 0; y < data_height; y++) + for (int x = 0; x < data_width; x++) + { + int offset = (y * data_width + x) * 3; + reverse[offset + 0] = data[offset + 2]; + reverse[offset + 1] = data[offset + 1]; + reverse[offset + 2] = data[offset + 0]; + } + + data_format = GL_RGB; + data = reverse; + } + else if (data_format == GL_BGRA) + { + std::vector reverse; + reverse.resize(data.size()); + + for (int y = 0; y < data_height; y++) + for (int x = 0; x < data_width; x++) + { + int offset = (y * data_width + x) * 4; + reverse[offset + 0] = data[offset + 2]; + reverse[offset + 1] = data[offset + 1]; + reverse[offset + 2] = data[offset + 0]; + reverse[offset + 3] = data[offset + 3]; + } + + data_format = GL_RGBA; + data = reverse; + } + + // if format matches, we're done + if (data_format == GL_RGBA && internalformat == GL_RGBA8) + return; + if (data_format == GL_RGB && internalformat == GL_RGB8) + return; + if (data_format == GL_RG && internalformat == GL_RG8) + return; + if (data_format == GL_RED && internalformat == GL_R8) + return; + + // do conversion + + int in_c = 0; + if (data_format == GL_RGBA) + in_c = 4; + else if (data_format == GL_RGB) + in_c = 3; + else if (data_format == GL_RG) + in_c = 2; + else if (data_format == GL_RED) + in_c = 1; + + int out_c = 0; + if (internalformat == GL_RGBA8) + out_c = 4; + else if (internalformat == GL_RGB8) + out_c = 3; + else if (internalformat == GL_RG8) + out_c = 2; + else if (internalformat == GL_R8) + out_c = 1; + + if (!in_c || !out_c) + return; // conversion not supported + + std::vector out; + out.resize(data_width * data_height * out_c); + + for (int y = 0; y < data_height; y++) + for (int x = 0; x < data_width; x++) + { + int pixel = (y * data_width + x); + int in_off = pixel * in_c; + int out_off = pixel * out_c; + for (int i = 0; i < out_c; i++) + { + if (i < in_c) + out[out_off + i] = data[in_off + i]; + else + out[out_off + i] = 0xFF; + } + } + + if (out_c == 4) + data_format = GL_RGBA; + else if (out_c == 3) + data_format = GL_RGB; + else if (out_c == 2) + data_format = GL_RG; + else if (out_c == 1) + data_format = GL_RED; + + data = out; +} + // loads texture data from specified file // TODO: wrap it in a workitem class, for the job system deferred loading void @@ -146,7 +335,7 @@ opengl_texture::load_BMP() { } data.resize( datasize ); - file.read( &data[0], datasize ); + file.read((char *)&data[0], datasize); // fill remaining data info if( info.bmiHeader.biBitCount == 32 ) { @@ -600,8 +789,9 @@ opengl_texture::unbind(size_t unit) bool opengl_texture::create() { - if( data_state != resource_state::good ) { + if( data_state != resource_state::good && !is_rendertarget ) { // don't bother until we have useful texture data + // and it isn't rendertarget texture without loaded data return false; } @@ -610,73 +800,125 @@ opengl_texture::create() { if( id == -1 ) { ::glGenTextures( 1, &id ); - ::glBindTexture( GL_TEXTURE_2D, id ); + ::glBindTexture( target, id ); // analyze specified texture traits - bool wraps{ true }; - bool wrapt{ true }; for( auto const &trait : traits ) { switch( trait ) { - case 's': { wraps = false; break; } - case 't': { wrapt = false; break; } + case 's': { wrap_mode_s = GL_CLAMP_TO_EDGE; break; } + case 't': { wrap_mode_t = GL_CLAMP_TO_EDGE; break; } } } - ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, ( wraps == true ? GL_REPEAT : GL_CLAMP_TO_EDGE ) ); - ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, ( wrapt == true ? GL_REPEAT : GL_CLAMP_TO_EDGE ) ); - - set_filtering(); - - if( data_mapcount == 1 ) { - // fill missing mipmaps if needed - ::glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE ); - } // upload texture data int dataoffset = 0, datasize = 0, datawidth = data_width, dataheight = data_height; - for( int maplevel = 0; maplevel < data_mapcount; ++maplevel ) { + if (is_rendertarget) + { + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(target, GL_TEXTURE_WRAP_S, wrap_mode_s); + glTexParameteri(target, GL_TEXTURE_WRAP_T, wrap_mode_t); + if (data_components == GL_DEPTH_COMPONENT) + { + glTexParameteri(target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); + float borderColor[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, borderColor); + } - if( ( data_format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ) - || ( data_format == GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ) - || ( data_format == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ) ) { - // compressed dds formats - int const datablocksize = - ( data_format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ? - 8 : - 16 ); - - datasize = ( ( std::max( datawidth, 4 ) + 3 ) / 4 ) * ( ( std::max( dataheight, 4 ) + 3 ) / 4 ) * datablocksize; - - ::glCompressedTexImage2D( - GL_TEXTURE_2D, maplevel, data_format, - datawidth, dataheight, 0, - datasize, (GLubyte *)&data[ dataoffset ] ); - - dataoffset += datasize; - datawidth = std::max( datawidth / 2, 1 ); - dataheight = std::max( dataheight / 2, 1 ); + if (Global.gfx_usegles) + { + if (target == GL_TEXTURE_2D || !glTexStorage2DMultisample) + glTexStorage2D(target, count_trailing_zeros(std::max(data_width, data_height)) + 1, data_format, data_width, data_height); + else if (target == GL_TEXTURE_2D_MULTISAMPLE) + glTexStorage2DMultisample(target, samples, data_format, data_width, data_height, GL_FALSE); } - else { - // uncompressed texture data. have the gfx card do the compression as it sees fit - ::glTexImage2D( - GL_TEXTURE_2D, 0, - ( Global.compress_tex ? - GL_COMPRESSED_RGBA : - GL_RGBA ), - data_width, data_height, 0, - data_format, GL_UNSIGNED_BYTE, (GLubyte *)&data[ 0 ] ); + else + { + if (target == GL_TEXTURE_2D) + glTexImage2D(target, 0, data_format, data_width, data_height, 0, data_components, GL_UNSIGNED_SHORT, nullptr); + else if (target == GL_TEXTURE_2D_MULTISAMPLE) + glTexImage2DMultisample(target, samples, data_format, data_width, data_height, GL_FALSE); } } + else + { + ::glTexParameteri(target, GL_TEXTURE_WRAP_S, wrap_mode_s); + ::glTexParameteri(target, GL_TEXTURE_WRAP_T, wrap_mode_t); + set_filtering(); - if( ( true == Global.ResourceMove ) - || ( false == Global.ResourceSweep ) ) { - // if garbage collection is disabled we don't expect having to upload the texture more than once - data = std::vector(); - data_state = resource_state::none; + // data_format and data_type specifies how image is laid out in memory + // data_components specifies what useful channels image contains + // components_hint specifies what format we want to load + + // now map that mess into opengl internal format + + GLint components = data_components; + auto f_it = precompressed_formats.find(data_format); + if (f_it != precompressed_formats.end()) + components = data_format; + + if (!components_hint) + components_hint = GL_SRGB_ALPHA; + + GLint internal_format = mapping[components][components_hint]; + + if (Global.gfx_usegles) + { + // GLES cannot generate mipmaps on SRGB8 + if (internal_format == GL_SRGB8) + internal_format = GL_SRGB8_ALPHA8; + + gles_match_internalformat(internal_format); + } + + auto blocksize_it = precompressed_formats.find(internal_format); + + for( int maplevel = 0; maplevel < data_mapcount; ++maplevel ) { + + if (blocksize_it != precompressed_formats.end()) + { + // compressed dds formats + const int datablocksize = blocksize_it->second; + + datasize = ( ( std::max( datawidth, 4 ) + 3 ) / 4 ) * ( ( std::max( dataheight, 4 ) + 3 ) / 4 ) * datablocksize; + + ::glCompressedTexImage2D( + target, maplevel, internal_format, + datawidth, dataheight, 0, + datasize, (GLubyte *)&data[ dataoffset ] ); + + dataoffset += datasize; + datawidth = std::max( datawidth / 2, 1 ); + dataheight = std::max( dataheight / 2, 1 ); + } + else { + GLint compressed_format = drivercompressed_formats[internal_format]; + + // uncompressed texture data. have the gfx card do the compression as it sees fit + ::glTexImage2D( + target, 0, + Global.compress_tex ? compressed_format : internal_format, + data_width, data_height, 0, + data_format, data_type, (GLubyte *)&data[ 0 ] ); + } + } + + if( data_mapcount == 1 ) { + // fill missing mipmaps if needed + glGenerateMipmap(target); + } + + if( ( true == Global.ResourceMove ) + || ( false == Global.ResourceSweep ) ) { + // if garbage collection is disabled we don't expect having to upload the texture more than once + data = std::vector(); + data_state = resource_state::none; + } } if( type == "make:" ) { diff --git a/Texture.h b/Texture.h index 7764c523..599b4eb0 100644 --- a/Texture.h +++ b/Texture.h @@ -73,11 +73,12 @@ private: void set_filtering() const; void downsize( GLuint const Format ); void flip_vertical(); + void gles_match_internalformat( GLuint format ); // members bool is_rendertarget = false; // is used as postfx rendertarget, without loaded data int samples = 1; - std::vector data; // texture data (stored GL-style, bottom-left origin) + std::vector data; // texture data (stored GL-style, bottom-left origin) resource_state data_state{ resource_state::none }; // current state of texture data int data_width{ 0 }, data_height{ 0 }, @@ -166,7 +167,7 @@ private: // reduces provided data image to half of original size, using basic 2x2 average template void -downsample( std::size_t const Width, std::size_t const Height, char *Imagedata ) { +downsample( std::size_t const Width, std::size_t const Height, unsigned char *Imagedata ) { Colortype_ *destination = reinterpret_cast( Imagedata ); Colortype_ *sampler = reinterpret_cast( Imagedata ); diff --git a/Track.cpp b/Track.cpp index 57f2decb..848bd369 100644 --- a/Track.cpp +++ b/Track.cpp @@ -1140,7 +1140,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { } if( ( Bank == 0 ) && ( false == Geometry2.empty() ) ) { // special variant, replace existing data for a turntable track - GfxRenderer->Replace( vertices, Geometry2[ 0 ] ); + GfxRenderer->Replace( vertices, Geometry2[ 0 ], GL_TRIANGLE_STRIP ); } } if (m_material1) @@ -1158,10 +1158,10 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { if( ( Bank == 0 ) && ( false == Geometry1.empty() ) ) { // special variant, replace existing data for a turntable track Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid > 0, texturelength ); - GfxRenderer->Replace( vertices, Geometry1[ 0 ] ); + GfxRenderer->Replace( vertices, Geometry1[ 0 ], GL_TRIANGLE_STRIP ); vertices.clear(); // reuse the scratchpad Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid > 0, texturelength ); - GfxRenderer->Replace( vertices, Geometry1[ 1 ] ); + GfxRenderer->Replace( vertices, Geometry1[ 1 ], GL_TRIANGLE_STRIP ); } } break; @@ -1789,7 +1789,7 @@ TTrack * TTrack::RaAnimate() // composed from two parts: transition from blade to regular rail, and regular rail SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, true, texturelength, 1.0, 0, bladelength / 2, { SwitchExtension->fOffset2, SwitchExtension->fOffset2 / 2 } ); SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength / 2, bladelength, { SwitchExtension->fOffset2 / 2, 0.f } ); - GfxRenderer->Replace( vertices, Geometry1[ 0 ] ); + GfxRenderer->Replace( vertices, Geometry1[ 0 ], GL_TRIANGLE_STRIP ); vertices.clear(); } if( m_material2 ) { @@ -1798,7 +1798,7 @@ TTrack * TTrack::RaAnimate() // composed from two parts: transition from blade to regular rail, and regular rail SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, true, texturelength, 1.0, 0, bladelength / 2, { -fMaxOffset + SwitchExtension->fOffset1, ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2 } ); SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength / 2, bladelength, { ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2, 0.f } ); - GfxRenderer->Replace( vertices, Geometry2[ 0 ] ); + GfxRenderer->Replace( vertices, Geometry2[ 0 ], GL_TRIANGLE_STRIP ); vertices.clear(); } } @@ -1809,7 +1809,7 @@ TTrack * TTrack::RaAnimate() // composed from two parts: transition from blade to regular rail, and regular rail SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, true, texturelength, 1.0, 0, bladelength / 2, { -SwitchExtension->fOffset2, -SwitchExtension->fOffset2 / 2 } ); SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength / 2, bladelength, { -SwitchExtension->fOffset2 / 2, 0.f } ); - GfxRenderer->Replace( vertices, Geometry1[ 0 ] ); + GfxRenderer->Replace( vertices, Geometry1[ 0 ], GL_TRIANGLE_STRIP ); vertices.clear(); } if( m_material2 ) { @@ -1818,7 +1818,7 @@ TTrack * TTrack::RaAnimate() // composed from two parts: transition from blade to regular rail, and regular rail SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, true, texturelength, 1.0, 0, bladelength / 2, { fMaxOffset - SwitchExtension->fOffset1, ( fMaxOffset - SwitchExtension->fOffset1 ) / 2 } ); SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength / 2, bladelength, { ( fMaxOffset - SwitchExtension->fOffset1 ) / 2, 0.f } ); - GfxRenderer->Replace( vertices, Geometry2[ 0 ] ); + GfxRenderer->Replace( vertices, Geometry2[ 0 ], GL_TRIANGLE_STRIP ); vertices.clear(); } } diff --git a/application.cpp b/application.cpp index 4edfabd8..ed3b553d 100644 --- a/application.cpp +++ b/application.cpp @@ -19,9 +19,11 @@ http://mozilla.org/MPL/2.0/. #include "dictionary.h" #include "sceneeditor.h" #include "openglrenderer.h" +#include "opengl33renderer.h" #include "uilayer.h" #include "translation.h" #include "Logs.h" +#include "Timer.h" #ifdef EU07_BUILD_STATIC #pragma comment( lib, "glfw3.lib" ) @@ -145,15 +147,27 @@ int eu07_application::run() { // main application loop - while( ( false == glfwWindowShouldClose( m_windows.front() ) ) - && ( false == m_modestack.empty() ) - && ( true == m_modes[ m_modestack.top() ]->update() ) - && ( true == GfxRenderer->Render() ) ) { + while (!glfwWindowShouldClose( m_windows.front() ) && !m_modestack.empty()) + { + Timer::subsystem.mainloop_total.start(); + + if( !m_modes[ m_modestack.top() ]->update() ) + break; + + if (!GfxRenderer->Render()) + break; + glfwPollEvents(); + + if (m_modestack.empty()) + return 0; + m_modes[ m_modestack.top() ]->on_event_poll(); + + Timer::subsystem.mainloop_total.stop(); } - return 0; + return 0; } // issues request for a worker thread to perform specified task. returns: true if task was scheduled @@ -532,9 +546,14 @@ eu07_application::init_gfx() { } } - { + if( Global.GfxRenderer == "default" ) { + // default render path + GfxRenderer = std::make_unique(); + } + else { // legacy render path GfxRenderer = std::make_unique(); + Global.GfxFramebufferSRGB = false; Global.DisabledLogTypes |= logtype::material; } diff --git a/drivermode.cpp b/drivermode.cpp index 1bc8eae0..a5359afd 100644 --- a/drivermode.cpp +++ b/drivermode.cpp @@ -238,10 +238,11 @@ driver_mode::update() { // NOTE: particle system runs on simulation time, but needs actual camera position to determine how to update each particle source simulation::Particles.update(); - GfxRenderer->Update( deltarealtime ); simulation::is_ready = true; + GfxRenderer->Update( deltarealtime ); + return true; } diff --git a/drivermouseinput.cpp b/drivermouseinput.cpp index a8dae764..25664f50 100644 --- a/drivermouseinput.cpp +++ b/drivermouseinput.cpp @@ -318,7 +318,7 @@ drivermouse_input::button( int const Button, int const Action ) { // left mouse button launches on_click event associated with to the node if( Button == GLFW_MOUSE_BUTTON_LEFT ) { if( Action == GLFW_PRESS ) { - GfxRenderer->Pick_Node( + GfxRenderer->Pick_Node_Callback( [this](scene::basic_node *node) { if( ( node == nullptr ) || ( typeid( *node ) != typeid( TAnimModel ) ) ) @@ -366,7 +366,7 @@ drivermouse_input::button( int const Button, int const Action ) { else { // if not release then it's press m_pickwaiting = true; - GfxRenderer->Pick_Control( + GfxRenderer->Pick_Control_Callback( [this, Button, Action, &mousecommand](TSubModel const *control) { bool pickwaiting = m_pickwaiting; diff --git a/driveruipanels.cpp b/driveruipanels.cpp index dbf53c15..df09a140 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -1104,9 +1104,14 @@ debug_panel::update_section_renderer( std::vector &Output ) { textline = std::string( "Rendering mode: " ) + + ( Global.GfxRenderer == "default" ? + "Shaders" : + ( Global.BasicRenderer ? + "Legacy Simple" : + "Legacy" ) ) + ( Global.bUseVBO ? - "VBO" : - "Display Lists" ) + ", VBO" : + ", Display Lists" ) + " "; if( false == Global.LastGLError.empty() ) { textline += diff --git a/editormode.cpp b/editormode.cpp index a60d278f..340fe2ff 100644 --- a/editormode.cpp +++ b/editormode.cpp @@ -230,7 +230,7 @@ editor_mode::on_mouse_button( int const Button, int const Action, int const Mods if( Action == GLFW_PRESS ) { // left button press m_node = nullptr; - GfxRenderer->Pick_Node( + GfxRenderer->Pick_Node_Callback( [ this ]( scene::basic_node *node ) { m_node = node; if( m_node ) { diff --git a/geometrybank.cpp b/geometrybank.cpp index 31ef6459..b0b656f6 100644 --- a/geometrybank.cpp +++ b/geometrybank.cpp @@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "geometrybank.h" #include "openglgeometrybank.h" +#include "opengl33geometrybank.h" #include "sn_utils.h" #include "Logs.h" @@ -239,8 +240,9 @@ geometrybank_manager::update() { gfx::geometrybank_handle geometrybank_manager::create_bank() { - if( true == Global.bUseVBO ) { m_geometrybanks.emplace_back( std::make_shared(), std::chrono::steady_clock::time_point() ); } - else { m_geometrybanks.emplace_back( std::make_shared(), std::chrono::steady_clock::time_point() ); } + if( Global.GfxRenderer == "default" ) { m_geometrybanks.emplace_back( std::make_shared(), std::chrono::steady_clock::time_point() ); } + else if( true == Global.bUseVBO ) { m_geometrybanks.emplace_back( std::make_shared(), std::chrono::steady_clock::time_point() ); } + else { m_geometrybanks.emplace_back( std::make_shared(), std::chrono::steady_clock::time_point() ); } // NOTE: handle is effectively (index into chunk array + 1) this leaves value of 0 to serve as error/empty handle indication return { static_cast( m_geometrybanks.size() ), 0 }; } diff --git a/maszyna.vcxproj b/maszyna.vcxproj new file mode 100644 index 00000000..c921fd3a --- /dev/null +++ b/maszyna.vcxproj @@ -0,0 +1,454 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {8E0232E5-1C67-442F-9E04-45ED2DDFC960} + Win32Proj + 10.0.17763.0 + + + + Application + true + v141_xp + + + Application + true + v141 + + + Application + false + v141_xp + + + Application + false + v141 + + + + + + + + + + + + + + + + + + + eu07-$(PlatformShortName) + $(SolutionDir)tmp\$(PlatformShortName)-$(Configuration)\$(ProjectName)\ + $(SolutionDir)bin\ + + + eu07-$(PlatformShortName) + $(SolutionDir)bin\ + $(SolutionDir)tmp\$(PlatformShortName)-$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)bin\ + $(SolutionDir)tmp\$(PlatformShortName)-$(Configuration)\$(ProjectName)\ + eu07-$(PlatformShortName) + + + eu07-$(PlatformShortName) + false + $(SolutionDir)bin\ + $(SolutionDir)tmp\$(PlatformShortName)-$(Configuration)\$(ProjectName)\ + + + + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + Level3 + ProgramDatabase + $(SolutionDir);$(SolutionDir)console;$(SolutionDir)mczapkie;$(SolutionDir)ref/glad/include;$(SolutionDir)ref/glfw/include;$(SolutionDir)ref/glm;$(SolutionDir)ref/openal/include;$(SolutionDir)ref/dr_libs/include;$(SolutionDir)ref/imgui;$(SolutionDir)ref/imgui/examples;$(SolutionDir)ref/python/include;$(SolutionDir)ref/libserialport/include;$(SolutionDir)ref/stb;%(AdditionalIncludeDirectories) + Use + true + false + true + true + stdcpp17 + + + true + $(DXSKD_DIR)lib/$(PlatformShortName);$(SolutionDir)ref/glew/lib/msvc-$(MSBuildToolsVersion)/$(PlatformShortName);$(SolutionDir)ref/glfw/lib/msvc-$(MSBuildToolsVersion)/$(PlatformShortName);$(SolutionDir)ref/openal/lib/$(PlatformShortName);$(SolutionDir)ref/python/lib/$(PlatformShortName);$(SolutionDir)ref/libserialport/lib/$(PlatformShortName);%(AdditionalLibraryDirectories) + Windows + + + true + + + + + + + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + Level3 + ProgramDatabase + $(SolutionDir);$(SolutionDir)console;$(SolutionDir)mczapkie;$(SolutionDir)ref/glad/include;$(SolutionDir)ref/glfw/include;$(SolutionDir)ref/glm;$(SolutionDir)ref/openal/include;$(SolutionDir)ref/dr_libs/include;$(SolutionDir)ref/imgui;$(SolutionDir)ref/imgui/examples;$(SolutionDir)ref/python/include;$(SolutionDir)ref/libserialport/include;$(SolutionDir)ref/stb;%(AdditionalIncludeDirectories) + Use + true + false + true + stdcpp17 + + + true + $(DXSKD_DIR)lib/$(PlatformShortName);$(SolutionDir)ref/glew/lib/msvc-$(MSBuildToolsVersion)/$(PlatformShortName);$(SolutionDir)ref/glfw/lib/msvc-$(MSBuildToolsVersion)/$(PlatformShortName);$(SolutionDir)ref/openal/lib/$(PlatformShortName);$(SolutionDir)ref/python/lib/$(PlatformShortName);$(SolutionDir)ref/libserialport/lib/$(PlatformShortName);%(AdditionalLibraryDirectories) + Windows + + + + + + + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreaded + Level3 + ProgramDatabase + $(SolutionDir);$(SolutionDir)console;$(SolutionDir)mczapkie;$(SolutionDir)ref/glad/include;$(SolutionDir)ref/glfw/include;$(SolutionDir)ref/glm;$(SolutionDir)ref/openal/include;$(SolutionDir)ref/dr_libs/include;$(SolutionDir)ref/imgui;$(SolutionDir)ref/imgui/examples;$(SolutionDir)ref/python/include;$(SolutionDir)ref/libserialport/include;$(SolutionDir)ref/stb;%(AdditionalIncludeDirectories) + Use + true + true + true + stdcpp17 + + + true + true + true + $(DXSKD_DIR)lib/$(PlatformShortName);$(SolutionDir)ref/glew/lib/msvc-$(MSBuildToolsVersion)/$(PlatformShortName);$(SolutionDir)ref/glfw/lib/msvc-$(MSBuildToolsVersion)/$(PlatformShortName);$(SolutionDir)ref/openal/lib/$(PlatformShortName);$(SolutionDir)ref/python/lib/$(PlatformShortName);$(SolutionDir)ref/libserialport/lib/$(PlatformShortName);%(AdditionalLibraryDirectories) + Windows + libcmt.lib + true + UseLinkTimeCodeGeneration + + + + + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreaded + Level3 + ProgramDatabase + $(SolutionDir);$(SolutionDir)console;$(SolutionDir)mczapkie;$(SolutionDir)ref/glad/include;$(SolutionDir)ref/glew/include;$(SolutionDir)ref/glfw/include;$(SolutionDir)ref/glm;$(SolutionDir)ref/openal/include;$(SolutionDir)ref/dr_libs/include;$(SolutionDir)ref/imgui;$(SolutionDir)ref/imgui/examples;$(SolutionDir)ref/python/include;$(SolutionDir)ref/libserialport/include;$(SolutionDir)ref/stb;%(AdditionalIncludeDirectories) + Use + true + true + true + true + stdcpp17 + + + true + true + true + $(DXSKD_DIR)lib/$(PlatformShortName);$(SolutionDir)ref/glew/lib/msvc-$(MSBuildToolsVersion)/$(PlatformShortName);$(SolutionDir)ref/glfw/lib/msvc-$(MSBuildToolsVersion)/$(PlatformShortName);$(SolutionDir)ref/openal/lib/$(PlatformShortName);$(SolutionDir)ref/python/lib/$(PlatformShortName);$(SolutionDir)ref/libserialport/lib/$(PlatformShortName);%(AdditionalLibraryDirectories) + Windows + libcmt.lib + UseLinkTimeCodeGeneration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + NotUsing + NotUsing + + + + + + + + + NotUsing + NotUsing + NotUsing + NotUsing + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TARGETMACHINEX86;%(PreprocessorDefinitions) + TARGETMACHINEX86;%(PreprocessorDefinitions) + + + + + + + + + \ No newline at end of file diff --git a/maszyna.vcxproj.filters b/maszyna.vcxproj.filters index c337709d..cee574f7 100644 --- a/maszyna.vcxproj.filters +++ b/maszyna.vcxproj.filters @@ -435,6 +435,12 @@ Source Files\gfx + + Source Files\gfx + + + Source Files\gfx + @@ -701,9 +707,6 @@ Header Files\gfx - - Header Files\gfx - Header Files\gfx @@ -800,6 +803,15 @@ Header Files\gfx + + Header Files\gfx + + + Header Files\gfx + + + Header Files\gfx + diff --git a/material.cpp b/material.cpp index 2dfeb068..69a0cc38 100644 --- a/material.cpp +++ b/material.cpp @@ -315,6 +315,15 @@ float opengl_material::get_or_guess_opacity() const { return 0.0f; } +bool +opengl_material::is_translucent() const { + + return ( + textures[ 0 ] != null_handle ? + GfxRenderer->Texture( textures[ 0 ] ).has_alpha : + false ); +} + // create material object from data stored in specified file. // NOTE: the deferred load parameter is passed to textures defined by material, the material itself is always loaded immediately material_handle diff --git a/material.h b/material.h index cbf4f11c..008707c1 100644 --- a/material.h +++ b/material.h @@ -38,9 +38,7 @@ struct opengl_material { deserialize( cParser &Input, bool const Loadnow ); void finalize(bool Loadnow); float get_or_guess_opacity() const; - inline bool - is_translucent() const { - return ( get_or_guess_opacity() == 0.0f ); } + bool is_translucent() const; private: // methods diff --git a/opengl33geometrybank.cpp b/opengl33geometrybank.cpp new file mode 100644 index 00000000..78324703 --- /dev/null +++ b/opengl33geometrybank.cpp @@ -0,0 +1,138 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#include "stdafx.h" +#include "opengl33geometrybank.h" + +#include "Logs.h" + +namespace gfx { + +// opengl vao/vbo-based variant of the geometry bank + +// create() subclass details +void +opengl33_vaogeometrybank::create_( gfx::geometry_handle const &Geometry ) { + // adding a chunk means we'll be (re)building the buffer, which will fill the chunk records, amongst other things. + // thus we don't need to initialize the values here + m_chunkrecords.emplace_back( chunk_record() ); + // kiss the existing buffer goodbye, new overall data size means we'll be making a new one + delete_buffer(); +} + +// replace() subclass details +void +opengl33_vaogeometrybank::replace_( gfx::geometry_handle const &Geometry ) { + + auto &chunkrecord = m_chunkrecords[ Geometry.chunk - 1 ]; + chunkrecord.is_good = false; + // if the overall length of the chunk didn't change we can get away with reusing the old buffer... + if( geometry_bank::chunk( Geometry ).vertices.size() != chunkrecord.size ) { + // ...but otherwise we'll need to allocate a new one + // TBD: we could keep and reuse the old buffer also if the new chunk is smaller than the old one, + // but it'd require some extra tracking and work to keep all chunks up to date; also wasting vram; may be not worth it? + delete_buffer(); + } +} + +void opengl33_vaogeometrybank::setup_buffer() +{ + if( !m_buffer ) { + // if there's no buffer, we'll have to make one + // NOTE: this isn't exactly optimal in terms of ensuring the gfx card doesn't stall waiting for the data + // may be better to initiate upload earlier (during update phase) and trust this effort won't go to waste + if( true == m_chunks.empty() ) { return; } + + std::size_t datasize{ 0 }; + auto chunkiterator = m_chunks.cbegin(); + for( auto &chunkrecord : m_chunkrecords ) { + // fill records for all chunks, based on the chunk data + chunkrecord.is_good = false; // if we're re-creating buffer, chunks might've been uploaded in the old one + chunkrecord.offset = datasize; + chunkrecord.size = chunkiterator->vertices.size(); + datasize += chunkrecord.size; + ++chunkiterator; + } + // the odds for all created chunks to get replaced with empty ones are quite low, but the possibility does exist + if( datasize == 0 ) { return; } + // try to set up the buffer we need + m_buffer.emplace(); + + // NOTE: we're using static_draw since it's generally true for all we have implemented at the moment + // TODO: allow to specify usage hint at the object creation, and pass it here + m_buffer->allocate(gl::buffer::ARRAY_BUFFER, datasize * sizeof(gfx::basic_vertex), GL_STATIC_DRAW); + + if( ::glGetError() == GL_OUT_OF_MEMORY ) { + ErrorLog( "openGL error: out of memory; failed to create a geometry buffer" ); + throw std::bad_alloc(); + } + m_buffercapacity = datasize; + } + + if (!m_vao) + { + m_vao.emplace(); + + m_vao->setup_attrib(*m_buffer, 0, 3, GL_FLOAT, sizeof(basic_vertex), 0 * sizeof(float)); + // NOTE: normal and color streams share the data + m_vao->setup_attrib(*m_buffer, 1, 3, GL_FLOAT, sizeof(basic_vertex), 3 * sizeof(float)); + m_vao->setup_attrib(*m_buffer, 2, 2, GL_FLOAT, sizeof(basic_vertex), 6 * sizeof(float)); + m_vao->setup_attrib(*m_buffer, 3, 4, GL_FLOAT, sizeof(basic_vertex), 8 * sizeof(float)); + + m_buffer->unbind(gl::buffer::ARRAY_BUFFER); + m_vao->unbind(); + } +} + +// draw() subclass details +// NOTE: units and stream parameters are unused, but they're part of (legacy) interface +// TBD: specialized bank/manager pair without the cruft? +void +opengl33_vaogeometrybank::draw_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, unsigned int const Streams ) +{ + setup_buffer(); + + auto &chunkrecord = m_chunkrecords.at(Geometry.chunk - 1); + // sanity check; shouldn't be needed but, eh + if( chunkrecord.size == 0 ) + return; + auto const &chunk = gfx::geometry_bank::chunk( Geometry ); + if( false == chunkrecord.is_good ) { + // we may potentially need to upload new buffer data before we can draw it + m_buffer->upload(gl::buffer::ARRAY_BUFFER, chunk.vertices.data(), + chunkrecord.offset * sizeof( gfx::basic_vertex ), + chunkrecord.size * sizeof( gfx::basic_vertex )); + chunkrecord.is_good = true; + } + // render + m_vao->bind(); + ::glDrawArrays( chunk.type, chunkrecord.offset, chunkrecord.size ); +} + +// release () subclass details +void +opengl33_vaogeometrybank::release_() { + + delete_buffer(); +} + +void +opengl33_vaogeometrybank::delete_buffer() { + + if( m_buffer ) { + + m_vao.reset(); + m_buffer.reset(); + m_buffercapacity = 0; + // NOTE: since we've deleted the buffer all chunks it held were rendered invalid as well + // instead of clearing their state here we're delaying it until new buffer is created to avoid looping through chunk records twice + } +} + +} // namespace gfx diff --git a/opengl33geometrybank.h b/opengl33geometrybank.h new file mode 100644 index 00000000..6ea4ed43 --- /dev/null +++ b/opengl33geometrybank.h @@ -0,0 +1,72 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include "geometrybank.h" +#include "gl/buffer.h" +#include "gl/vao.h" + +namespace gfx { + +// opengl vao/vbo-based variant of the geometry bank + +class opengl33_vaogeometrybank : public geometry_bank { + +public: +// constructors: + opengl33_vaogeometrybank() = default; +// destructor + ~opengl33_vaogeometrybank() { + delete_buffer(); } +// methods: + static + void + reset() {;} + +private: +// types: + struct chunk_record{ + std::size_t offset{ 0 }; // beginning of the chunk data as offset from the beginning of the last established buffer + std::size_t size{ 0 }; // size of the chunk in the last established buffer + bool is_good{ false }; // true if local content of the chunk matches the data on the opengl end + }; + + typedef std::vector chunkrecord_sequence; + +// methods: + // create() subclass details + void + create_( gfx::geometry_handle const &Geometry ) override; + // replace() subclass details + void + replace_( gfx::geometry_handle const &Geometry ) override; + // draw() subclass details + void + draw_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, unsigned int const Streams ) override; + // release() subclass details + void + release_() override; + void + setup_buffer(); + void + delete_buffer(); + +// members: + std::optional m_buffer; // buffer data on the opengl end + std::optional m_vao; + std::size_t m_buffercapacity{ 0 }; // total capacity of the last established buffer + chunkrecord_sequence m_chunkrecords; // helper data for all stored geometry chunks, in matching order + // vectors for glMultiDrawArrays in class scope + // to don't waste time on reallocating + std::vector m_offsets; + std::vector m_counts; +}; + +} // namespace gfx \ No newline at end of file diff --git a/opengl33particles.cpp b/opengl33particles.cpp index eb019212..9ce63677 100644 --- a/opengl33particles.cpp +++ b/opengl33particles.cpp @@ -13,6 +13,7 @@ http://mozilla.org/MPL/2.0/. #include "particles.h" #include "openglcamera.h" #include "simulation.h" +#include "simulationenvironment.h" std::vector> const billboard_vertices { @@ -53,7 +54,8 @@ opengl33_particles::update( opengl_camera const &Camera ) { auto const particlecolor { glm::clamp( source.second.color() - * ( glm::vec3 { Global.DayLight.ambient } + 0.35f * glm::vec3{ Global.DayLight.diffuse } ), + * ( glm::vec3 { Global.DayLight.ambient } + + 0.35f * glm::vec3{ Global.DayLight.diffuse } ) * simulation::Environment.light_intensity(), glm::vec3{ 0.f }, glm::vec3{ 1.f } ) }; auto const &particles { source.second.sequence() }; // TODO: put sanity cap on the overall amount of particles that can be drawn @@ -65,7 +67,7 @@ opengl33_particles::update( opengl_camera const &Camera ) { vertex.color[ 0 ] = particlecolor.r; vertex.color[ 1 ] = particlecolor.g; vertex.color[ 2 ] = particlecolor.b; - vertex.color.a = std::clamp(particle.opacity, 0.0f, 1.0f); + vertex.color.a = clamp(particle.opacity, 0.0f, 1.0f); auto const offset { glm::vec3{ particle.position - Camera.position() } }; auto const rotation { glm::angleAxis( particle.rotation, glm::vec3{ 0.f, 0.f, 1.f } ) }; diff --git a/opengl33renderer.cpp b/opengl33renderer.cpp index 027fb9b7..8b1edf9e 100644 --- a/opengl33renderer.cpp +++ b/opengl33renderer.cpp @@ -24,6 +24,8 @@ http://mozilla.org/MPL/2.0/. int const EU07_PICKBUFFERSIZE{1024}; // size of (square) textures bound with the pick framebuffer +auto const gammacorrection { glm::vec3( 2.2f ) }; + bool opengl33_renderer::Init(GLFWwindow *Window) { if (!Init_caps()) @@ -280,7 +282,7 @@ bool opengl33_renderer::init_viewport(viewport_config &vp) glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ALIGNMENT, 1); - glClearColor(51.0f / 255.0f, 102.0f / 255.0f, 85.0f / 255.0f, 1.0f); // initial background Color + glClearColor( 51.0f / 255.f, 102.0f / 255.f, 85.0f / 255.f, 1.f ); // initial background Color glFrontFace(GL_CCW); glEnable(GL_CULL_FACE); @@ -397,6 +399,8 @@ bool opengl33_renderer::Render() m_sunlight.direction = glm::normalize(quantizationstep * glm::roundEven(m_sunlight.direction * (1.f / quantizationstep))); } // generate new frame + opengl_texture::reset_unit_cache(); + m_renderpass.draw_mode = rendermode::none; // force setup anew m_debugstats = debug_stats(); @@ -431,6 +435,8 @@ bool opengl33_renderer::Render() ++m_framestamp; + SwapBuffers(); + Timer::subsystem.gfx_total.stop(); return true; // for now always succeed @@ -491,10 +497,13 @@ void opengl33_renderer::Render_pass(viewport_config &vp, rendermode const Mode) if (!simulation::is_ready) { gl::framebuffer::unbind(); - glClearColor(0.0f, 0.5f, 0.0f, 1.0f); + glClearColor( 51.0f / 255.f, 102.0f / 255.f, 85.0f / 255.f, 1.f ); // initial background Color glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - if (vp.main) - Application.render_ui(); + if( vp.main ) { + //glEnable( GL_FRAMEBUFFER_SRGB ); + Application.render_ui(); + //glDisable( GL_FRAMEBUFFER_SRGB ); + } break; } @@ -952,49 +961,17 @@ void opengl33_renderer::setup_pass(viewport_config &Viewport, renderpass_config Config.draw_mode = Mode; - if (false == simulation::is_ready) - { - return; - } - // setup draw range - switch (Mode) - { - case rendermode::color: - { - Config.draw_range = Global.BaseDrawRange; - break; - } - case rendermode::shadows: - { - Config.draw_range = Global.BaseDrawRange * 0.5f; - break; - } - case rendermode::cabshadows: - { - Config.draw_range = simulation::Train->Occupied()->Dim.L; - break; - } - case rendermode::reflections: - { - Config.draw_range = Global.BaseDrawRange; - break; - } - case rendermode::pickcontrols: - { - Config.draw_range = 50.f; - break; - } - case rendermode::pickscenery: - { - Config.draw_range = Global.BaseDrawRange * 0.5f; - break; - } - default: - { - Config.draw_range = 0.f; - break; - } - } + if( false == simulation::is_ready ) { return; } + // setup draw range + switch( Mode ) { + case rendermode::color: { Config.draw_range = Global.BaseDrawRange; break; } + case rendermode::shadows: { Config.draw_range = Global.BaseDrawRange * 0.5f; break; } + case rendermode::cabshadows: { Config.draw_range = ( Global.RenderCabShadowsRange > 0 ? clamp( Global.RenderCabShadowsRange, 5, 100 ) : simulation::Train->Occupied()->Dim.L ); break; } + case rendermode::reflections: { Config.draw_range = Global.BaseDrawRange; break; } + case rendermode::pickcontrols: { Config.draw_range = 50.f; break; } + case rendermode::pickscenery: { Config.draw_range = Global.BaseDrawRange * 0.5f; break; } + default: { Config.draw_range = 0.f; break; } + } Config.draw_range *= Viewport.draw_range; @@ -1247,7 +1224,12 @@ void opengl33_renderer::setup_shadow_map(opengl_texture *tex, renderpass_config glm::mat4 depthcam = conf.pass_camera.modelview(); glm::mat4 worldcam = m_renderpass.pass_camera.modelview(); - scene_ubs.lightview = coordmove * depthproj * depthcam * glm::inverse(worldcam); + scene_ubs.lightview = + coordmove + * depthproj + * depthcam + * glm::inverse( worldcam ); + scene_ubo->update(scene_ubs); } } @@ -1328,12 +1310,18 @@ bool opengl33_renderer::Render(world_environment *Environment) // drawn with 500m radius to blend in if the fog range is low glPushMatrix(); glScalef(500.0f, 500.0f, 500.0f); + m_skydomerenderer.update(); m_skydomerenderer.render(); glPopMatrix(); // skydome uses a custom vbo which could potentially confuse the main geometry system. hardly elegant but, eh gfx::opengl_vbogeometrybank::reset(); + ::glPushAttrib( GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT ); + ::glDisable( GL_ALPHA_TEST ); + ::glEnable( GL_BLEND ); + ::glBlendFunc( GL_SRC_ALPHA, GL_ONE ); + // stars if (Environment->m_stars.m_stars != nullptr) { @@ -1349,43 +1337,28 @@ bool opengl33_renderer::Render(world_environment *Environment) ::glPopMatrix(); } - auto const fogfactor{clamp(Global.fFogEnd / 2000.f, 0.f, 1.f)}; // stronger fog reduces opacity of the celestial bodies - float const duskfactor = 1.0f - clamp(std::abs(Environment->m_sun.getAngle()), 0.0f, 12.0f) / 12.0f; - glm::vec3 suncolor = interpolate(glm::vec3(255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f), glm::vec3(235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f), duskfactor); - - // clouds - if (Environment->m_clouds.mdCloud) - { - // setup - glm::vec3 color = interpolate(Environment->m_skydome.GetAverageColor(), suncolor, duskfactor * 0.25f) * interpolate(1.f, 0.35f, Global.Overcast / 2.f) // overcast darkens the clouds - * 0.5f; - - // write cloud color into material - TSubModel *mdl = Environment->m_clouds.mdCloud->Root; - if (mdl->m_material != null_handle) - m_materials.material(mdl->m_material).params[0] = glm::vec4(color, 1.0f); - - // render - Render(Environment->m_clouds.mdCloud, nullptr, 100.0); - Render_Alpha(Environment->m_clouds.mdCloud, nullptr, 100.0); - // post-render cleanup - } - // celestial bodies - m_celestial_shader->bind(); m_empty_vao->bind(); auto const &modelview = OpenGLMatrices.data(GL_MODELVIEW); + auto const fogfactor{clamp(Global.fFogEnd / 2000.f, 0.f, 1.f)}; // stronger fog reduces opacity of the celestial bodies + float const duskfactor = 1.0f - clamp(std::abs(Environment->m_sun.getAngle()), 0.0f, 12.0f) / 12.0f; + glm::vec3 suncolor = interpolate(glm::vec3(255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f), glm::vec3(235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f), duskfactor); + // sun { Bind_Texture(0, m_suntexture); glm::vec4 color(suncolor.x, suncolor.y, suncolor.z, clamp(1.5f - Global.Overcast, 0.f, 1.f) * fogfactor); auto const sunvector = Environment->m_sun.getDirection(); + float const size = interpolate( // TODO: expose distance/scale factor from the moon object + 0.0325f, + 0.0275f, + clamp( Environment->m_sun.getAngle(), 0.f, 90.f ) / 90.f ); model_ubs.param[0] = color; - model_ubs.param[1] = glm::vec4(glm::vec3(modelview * glm::vec4(sunvector, 1.0f)), 0.00463f); + model_ubs.param[1] = glm::vec4(glm::vec3(modelview * glm::vec4(sunvector, 1.0f)), /*0.00463f*/ size); model_ubs.param[2] = glm::vec4(0.0f, 1.0f, 1.0f, 0.0f); model_ubo->update(model_ubs); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); @@ -1451,12 +1424,36 @@ bool opengl33_renderer::Render(world_environment *Environment) moonu = 0.0f; } + float const size = interpolate( // TODO: expose distance/scale factor from the moon object + 0.0160f, + 0.0135f, + clamp( Environment->m_moon.getAngle(), 0.f, 90.f ) / 90.f ); + model_ubs.param[0] = color; - model_ubs.param[1] = glm::vec4(glm::vec3(modelview * glm::vec4(moonvector, 1.0f)), 0.00451f); + model_ubs.param[1] = glm::vec4(glm::vec3(modelview * glm::vec4(moonvector, 1.0f)), /*0.00451f*/ size); model_ubs.param[2] = glm::vec4(moonu, moonv, 0.333f, 0.0f); model_ubo->update(model_ubs); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } + ::glPopAttrib(); + + // clouds + if (Environment->m_clouds.mdCloud) + { + // setup + glm::vec3 color = interpolate(Environment->m_skydome.GetAverageColor(), suncolor, duskfactor * 0.25f) * interpolate(1.f, 0.35f, Global.Overcast / 2.f) // overcast darkens the clouds + * 0.5f; + + // write cloud color into material + TSubModel *mdl = Environment->m_clouds.mdCloud->Root; + if (mdl->m_material != null_handle) + m_materials.material(mdl->m_material).params[0] = glm::vec4(color, 1.0f); + + // render + Render(Environment->m_clouds.mdCloud, nullptr, 100.0); + Render_Alpha(Environment->m_clouds.mdCloud, nullptr, 100.0); + // post-render cleanup + } gl::program::unbind(); gl::vao::unbind(); @@ -1548,6 +1545,7 @@ void opengl33_renderer::Bind_Material(material_handle const Material, TSubModel src = sm->f4Ambient; else if (entry.defaultparam == gl::shader::defaultparam_e::diffuse) src = sm->f4Diffuse; +// src = glm::vec4( glm::pow( glm::vec3( sm->f4Diffuse ), gammacorrection ), sm->f4Diffuse.a ); else if (entry.defaultparam == gl::shader::defaultparam_e::specular) src = sm->f4Specular; } @@ -1645,6 +1643,11 @@ texture_handle opengl33_renderer::Fetch_Texture(std::string const &Filename, boo return m_textures.create(Filename, Loadnow, format_hint); } +void opengl33_renderer::Bind_Texture( texture_handle const Texture ) +{ + return Bind_Texture( 0, Texture ); +} + void opengl33_renderer::Bind_Texture(std::size_t const Unit, texture_handle const Texture) { m_textures.bind(Unit, Texture); @@ -1720,7 +1723,7 @@ void opengl33_renderer::Render(scene::basic_region *Region) { // when editor mode is active calculate world position of the cursor // at this stage the z-buffer is filled with only ground geometry - get_mouse_depth(); + Update_Mouse_Position(); } Render(std::begin(m_cellqueue), std::end(m_cellqueue)); break; @@ -2489,7 +2492,7 @@ void opengl33_renderer::Render(TSubModel *Submodel) Bind_Material(Submodel->m_material, Submodel); // main draw call - model_ubs.param[1].x = 2.0f * 2.0f; + model_ubs.param[1].x = 2.0f; draw(Submodel->m_geometry); } @@ -3353,9 +3356,10 @@ void opengl33_renderer::Render_Alpha(TSubModel *Submodel) // main draw call model_ubs.emission = 1.0f; - auto const lightcolor = glm::vec3(Submodel->DiffuseOverride.r < 0.f ? // -1 indicates no override - Submodel->f4Diffuse : - Submodel->DiffuseOverride); + auto lightcolor = glm::vec3(Submodel->DiffuseOverride.r < 0.f ? // -1 indicates no override + Submodel->f4Diffuse : + Submodel->DiffuseOverride); + lightcolor = glm::pow( lightcolor, gammacorrection ); m_freespot_shader->bind(); @@ -3368,7 +3372,7 @@ void opengl33_renderer::Render_Alpha(TSubModel *Submodel) draw(Submodel->m_geometry); } - model_ubs.param[1].x = pointsize * resolutionratio * 2.0f; + model_ubs.param[1].x = pointsize * resolutionratio; model_ubs.param[0] = glm::vec4(glm::vec3(lightcolor), Submodel->fVisible * std::min(1.f, lightlevel)); if (!Submodel->occlusion_query) @@ -3517,17 +3521,17 @@ void opengl33_renderer::Update_Pick_Node() } } -void opengl33_renderer::pick_control(std::function callback) +void opengl33_renderer::Pick_Control_Callback(std::function callback) { m_control_pick_requests.push_back(callback); } -void opengl33_renderer::pick_node(std::function callback) +void opengl33_renderer::Pick_Node_Callback(std::function callback) { m_node_pick_requests.push_back(callback); } -glm::dvec3 opengl33_renderer::get_mouse_depth() +glm::dvec3 opengl33_renderer::Update_Mouse_Position() { if (!m_depth_pointer_pbo->is_busy()) { @@ -3631,18 +3635,33 @@ void opengl33_renderer::Update(double const Deltatime) } m_updateaccumulator = 0.0; - m_framerate = 1000.f / (Timer::subsystem.mainloop_total.average()); + m_framerate = 1000.f / ( Timer::subsystem.mainloop_total.average() ); // adjust draw ranges etc, based on recent performance // TODO: it doesn't make much sense with vsync - - if (Global.targetfps != 0.0f) { - float fps_diff = Global.targetfps - m_framerate; - if (fps_diff > 0.0f) - Global.fDistanceFactor = std::max(0.5f, Global.fDistanceFactor - 0.05f); - else - Global.fDistanceFactor = std::min(3.0f, Global.fDistanceFactor + 0.05f); - } + if( Global.targetfps == 0.0f ) { + // automatic adjustment + float targetfactor; + if( m_framerate > 90.0 ) { targetfactor = 3.0f; } + else if( m_framerate > 60.0 ) { targetfactor = 1.5f; } + else if( m_framerate > 30.0 ) { targetfactor = 1.25; } + else { targetfactor = 1.0f; } + if( targetfactor > Global.fDistanceFactor ) { + Global.fDistanceFactor = std::min( targetfactor, Global.fDistanceFactor + 0.05f ); + } + else if( targetfactor < Global.fDistanceFactor ) { + Global.fDistanceFactor = std::max( targetfactor, Global.fDistanceFactor - 0.05f ); + } + } + else { + auto const fps_diff = Global.targetfps - m_framerate; + if( fps_diff > 0.5f ) { + Global.fDistanceFactor = std::max( 1.0f, Global.fDistanceFactor - 0.05f ); + } + else if( fps_diff < 0.5f ) { + Global.fDistanceFactor = std::min( 3.0f, Global.fDistanceFactor + 0.05f ); + } + } if ((true == Global.ResourceSweep) && (true == simulation::is_ready)) { @@ -3652,10 +3671,10 @@ void opengl33_renderer::Update(double const Deltatime) } if ((true == Global.ControlPicking) && (false == FreeFlyModeFlag)) - pick_control([](const TSubModel *) {}); + Pick_Control_Callback([](const TSubModel *) {}); // temporary conditions for testing. eventually will be coupled with editor mode if ((true == Global.ControlPicking) && (true == DebugModeFlag) && (true == FreeFlyModeFlag)) - pick_node([](scene::basic_node *) {}); + Pick_Node_Callback([](scene::basic_node *) {}); // dump last opengl error, if any auto const glerror = ::glGetError(); @@ -3748,7 +3767,7 @@ void opengl33_renderer::Update_Lights(light_array &Lights) renderlight->diffuse = glm::vec4{glm::max(glm::vec3{colors::none}, scenelight.color - glm::vec3{luminance}), renderlight->diffuse[3]}; renderlight->ambient = glm::vec4{glm::max(glm::vec3{colors::none}, scenelight.color * glm::vec3{scenelight.intensity} - glm::vec3{luminance}), renderlight->ambient[3]}; - renderlight->apply_intensity(); + renderlight->apply_intensity( ( scenelight.count * 0.5 ) * ( scenelight.owner->DimHeadlights ? 0.5 : 1.0 ) ); renderlight->apply_angle(); gl::light_element_ubs *l = &light_ubs.lights[light_i]; @@ -3767,10 +3786,10 @@ void opengl33_renderer::Update_Lights(light_array &Lights) ++renderlight; } - light_ubs.ambient = m_sunlight.ambient * m_sunlight.factor; + light_ubs.ambient = m_sunlight.ambient * m_sunlight.factor;// *simulation::Environment.light_intensity(); light_ubs.lights[0].type = gl::light_element_ubs::DIR; light_ubs.lights[0].dir = mv * glm::vec4(m_sunlight.direction, 0.0f); - light_ubs.lights[0].color = m_sunlight.diffuse * m_sunlight.factor; + light_ubs.lights[0].color = m_sunlight.diffuse * m_sunlight.factor * simulation::Environment.light_intensity(); light_ubs.lights[0].ambient = 0.0f; light_ubs.lights[0].intensity = 1.0f; light_ubs.lights_count = light_i; diff --git a/opengl33renderer.h b/opengl33renderer.h index a4c8e408..bd7e2cb3 100644 --- a/opengl33renderer.h +++ b/opengl33renderer.h @@ -13,7 +13,7 @@ http://mozilla.org/MPL/2.0/. #include "openglcamera.h" #include "opengl33light.h" #include "opengl33particles.h" -#include "openglskydome.h" +#include "opengl33skydome.h" #include "opengl33precipitation.h" #include "simulationenvironment.h" #include "scene.h" @@ -33,51 +33,52 @@ http://mozilla.org/MPL/2.0/. class opengl33_renderer : public gfx_renderer { public: - // types + // types /// Renderer runtime settings struct Settings { bool traction_debug { false }; } settings; - - // methods - bool Init(GLFWwindow *Window); -/* - bool AddViewport(const global_settings::extraviewport_config &conf); - */ - // main draw call. returns false on error - bool Render(); - void SwapBuffers(); - inline float Framerate() - { - return m_framerate; - } - // geometry methods - // NOTE: hands-on geometry management is exposed as a temporary measure; ultimately all visualization data should be generated/handled automatically by the renderer itself - // creates a new geometry bank. returns: handle to the bank or NULL - gfx::geometrybank_handle Create_Bank(); - // creates a new geometry chunk of specified type from supplied vertex data, in specified bank. returns: handle to the chunk or NULL - gfx::geometry_handle Insert(gfx::vertex_array &Vertices, gfx::geometrybank_handle const &Geometry, int const Type); - // replaces data of specified chunk with the supplied vertex data, starting from specified offset - bool Replace(gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, int const Type, std::size_t const Offset = 0); - // adds supplied vertex data at the end of specified chunk - bool Append(gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, int const Type); - // draws supplied geometry handles - void Draw_Geometry(std::vector::iterator begin, std::vector::iterator end); - void Draw_Geometry(const gfx::geometrybank_handle &handle); - // provides direct access to vertex data of specfied chunk - gfx::vertex_array const &Vertices(gfx::geometry_handle const &Geometry) const; - // material methods - material_handle Fetch_Material(std::string const &Filename, bool const Loadnow = true); - void Bind_Material(material_handle const Material, TSubModel *sm = nullptr); - void Bind_Material_Shadow(material_handle const Material); - - // shader methods - std::shared_ptr Fetch_Shader( std::string const &name ) override; - - opengl_material const &Material(material_handle const Material) const; - opengl_material &Material(material_handle const Material); - // texture methods +// constructors + opengl33_renderer() = default; +// destructor + ~opengl33_renderer() {} +// methods + bool + Init( GLFWwindow *Window ) override; + // main draw call. returns false on error + bool + Render() override; + inline + float + Framerate() override { return m_framerate; } + // geometry methods + // NOTE: hands-on geometry management is exposed as a temporary measure; ultimately all visualization data should be generated/handled automatically by the renderer itself + // creates a new geometry bank. returns: handle to the bank or NULL + gfx::geometrybank_handle + Create_Bank() override; + // creates a new geometry chunk of specified type from supplied vertex data, in specified bank. returns: handle to the chunk or NULL + gfx::geometry_handle + Insert( gfx::vertex_array &Vertices, gfx::geometrybank_handle const &Geometry, int const Type ) override; + // replaces data of specified chunk with the supplied vertex data, starting from specified offset + bool + Replace( gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, int const Type, std::size_t const Offset = 0 ) override; + // adds supplied vertex data at the end of specified chunk + bool + Append( gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, int const Type ) override; + // provides direct access to vertex data of specfied chunk + gfx::vertex_array const & + Vertices( gfx::geometry_handle const &Geometry ) const override; + // material methods + material_handle + Fetch_Material( std::string const &Filename, bool const Loadnow = true ) override; + void + Bind_Material( material_handle const Material, TSubModel *sm = nullptr ) override; + opengl_material const & + Material( material_handle const Material ) const override; + // shader methods + auto Fetch_Shader( std::string const &name ) -> std::shared_ptr override; + // texture methods texture_handle Fetch_Texture( std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA ) override; void @@ -88,25 +89,42 @@ class opengl33_renderer : public gfx_renderer { Texture( texture_handle const Texture ) override; opengl_texture const & Texture( texture_handle const Texture ) const override; - // utility methods + // utility methods + void + Pick_Control_Callback( std::function Callback ) override; + void + Pick_Node_Callback( std::function Callback ) override; TSubModel const * Pick_Control() const override { return m_pickcontrolitem; } scene::basic_node const * Pick_Node() const override { return m_picksceneryitem; } glm::dvec3 Mouse_Position() const override { return m_worldmousecoordinates; } - void Update_AnimModel(TAnimModel *model); - // maintenance methods - void Update(double const Deltatime); - void Update_Pick_Control(); - void Update_Pick_Node(); - glm::dvec3 get_mouse_depth(); - // debug methods - std::string const &info_times() const; - std::string const &info_stats() const; + // maintenance methods + void + Update( double const Deltatime ) override; + void + Update_Pick_Control() override; + void + Update_Pick_Node() override; + glm::dvec3 + Update_Mouse_Position() override; + // debug methods + std::string const & + info_times() const override; + std::string const & + info_stats() const override; - void pick_control(std::function callback); - void pick_node(std::function callback); + + + opengl_material & Material( material_handle const Material ); + void SwapBuffers(); + // draws supplied geometry handles + void Draw_Geometry(std::vector::iterator begin, std::vector::iterator end); + void Draw_Geometry(const gfx::geometrybank_handle &handle); + // material methods + void Bind_Material_Shadow(material_handle const Material); + void Update_AnimModel(TAnimModel *model); // members GLenum static const sunlight{0}; @@ -252,7 +270,7 @@ class opengl33_renderer : public gfx_renderer { int m_environmentcubetextureface{0}; // helper, currently processed cube map face double m_environmentupdatetime{0}; // time of the most recent environment map update glm::dvec3 m_environmentupdatelocation; // coordinates of most recent environment map update - opengl_skydome m_skydomerenderer; + opengl33_skydome m_skydomerenderer; opengl33_precipitation m_precipitationrenderer; opengl33_particles m_particlerenderer; // particle visualization subsystem diff --git a/opengl33skydome.cpp b/opengl33skydome.cpp new file mode 100644 index 00000000..2054a6d0 --- /dev/null +++ b/opengl33skydome.cpp @@ -0,0 +1,69 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#include "stdafx.h" +#include "opengl33skydome.h" + +#include "simulationenvironment.h" + +void opengl33_skydome::update() { + + if (!m_shader) { + + gl::shader vert("vbocolor.vert"); + gl::shader frag("color.frag"); + m_shader.emplace(std::vector>({vert, frag})); + } + + auto &skydome { simulation::Environment.skydome() }; + + if (!m_vertexbuffer) { + m_vao.emplace(); + m_vao->bind(); + + m_vertexbuffer.emplace(); + m_vertexbuffer->allocate(gl::buffer::ARRAY_BUFFER, skydome.vertices().size() * sizeof( glm::vec3 ), GL_STATIC_DRAW); + m_vertexbuffer->upload(gl::buffer::ARRAY_BUFFER, skydome.vertices().data(), 0, skydome.vertices().size() * sizeof( glm::vec3 )); + + m_vao->setup_attrib(*m_vertexbuffer, 0, 3, GL_FLOAT, sizeof(glm::vec3), 0); + + m_coloursbuffer.emplace(); + m_coloursbuffer->allocate(gl::buffer::ARRAY_BUFFER, skydome.colors().size() * sizeof( glm::vec3 ), GL_STATIC_DRAW); + m_coloursbuffer->upload(gl::buffer::ARRAY_BUFFER, skydome.colors().data(), 0, skydome.colors().size() * sizeof( glm::vec3 )); + + m_vao->setup_attrib(*m_coloursbuffer, 1, 3, GL_FLOAT, sizeof(glm::vec3), 0); + + m_indexbuffer.emplace(); + m_indexbuffer->allocate(gl::buffer::ELEMENT_ARRAY_BUFFER, skydome.indices().size() * sizeof( unsigned short ), GL_STATIC_DRAW); + m_indexbuffer->upload(gl::buffer::ELEMENT_ARRAY_BUFFER, skydome.indices().data(), 0, skydome.indices().size() * sizeof( unsigned short )); + m_vao->setup_ebo(*m_indexbuffer); + + m_vao->unbind(); + } + // ship the current dynamic data to the gpu + if( true == skydome.is_dirty() ) { + if( m_coloursbuffer ) { + // the colour buffer was already initialized, so on this run we update its content + m_coloursbuffer->upload( gl::buffer::ARRAY_BUFFER, skydome.colors().data(), 0, skydome.colors().size() * sizeof( glm::vec3 ) ); + skydome.is_dirty() = false; + } + } +} + +// render skydome to screen +void opengl33_skydome::render() { + + if( ( !m_shader) || ( !m_vao ) ) { return; } + + m_shader->bind(); + m_vao->bind(); + + auto const &skydome { simulation::Environment.skydome() }; + ::glDrawElements( GL_TRIANGLES, static_cast( skydome.indices().size() ), GL_UNSIGNED_SHORT, reinterpret_cast( 0 ) ); +} diff --git a/opengl33skydome.h b/opengl33skydome.h new file mode 100644 index 00000000..425c276b --- /dev/null +++ b/opengl33skydome.h @@ -0,0 +1,36 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include "gl/vao.h" +#include "gl/shader.h" +#include "gl/buffer.h" + +class opengl33_skydome { + +public: +// constructors + opengl33_skydome() = default; +// destructor + ~opengl33_skydome() {;} +// methods + // updates data stores on the opengl end. NOTE: unbinds buffers + void update(); + // draws the skydome + void render(); + +private: +// members + std::optional m_vertexbuffer; + std::optional m_indexbuffer; + std::optional m_coloursbuffer; + std::optional m_shader; + std::optional m_vao; +}; diff --git a/openglgeometrybank.cpp b/openglgeometrybank.cpp index 1b0d6058..f58a59ad 100644 --- a/openglgeometrybank.cpp +++ b/openglgeometrybank.cpp @@ -49,44 +49,8 @@ opengl_vbogeometrybank::replace_( gfx::geometry_handle const &Geometry ) { void opengl_vbogeometrybank::draw_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, unsigned int const Streams ) { - if( m_buffer == 0 ) { - // if there's no buffer, we'll have to make one - // NOTE: this isn't exactly optimal in terms of ensuring the gfx card doesn't stall waiting for the data - // may be better to initiate upload earlier (during update phase) and trust this effort won't go to waste - if( true == m_chunks.empty() ) { return; } + setup_buffer(); - std::size_t datasize{ 0 }; - auto chunkiterator = m_chunks.cbegin(); - for( auto &chunkrecord : m_chunkrecords ) { - // fill records for all chunks, based on the chunk data - chunkrecord.is_good = false; // if we're re-creating buffer, chunks might've been uploaded in the old one - chunkrecord.offset = datasize; - chunkrecord.size = chunkiterator->vertices.size(); - datasize += chunkrecord.size; - ++chunkiterator; - } - // the odds for all created chunks to get replaced with empty ones are quite low, but the possibility does exist - if( datasize == 0 ) { return; } - // try to set up the buffer we need - ::glGenBuffers( 1, &m_buffer ); - bind_buffer(); - if( m_buffer == 0 ) { return; } // if we didn't get a buffer we'll try again during the next draw call - // NOTE: we're using static_draw since it's generally true for all we have implemented at the moment - // TODO: allow to specify usage hint at the object creation, and pass it here - ::glBufferData( - GL_ARRAY_BUFFER, - datasize * sizeof( gfx::basic_vertex ), - nullptr, - GL_STATIC_DRAW ); - if( ::glGetError() == GL_OUT_OF_MEMORY ) { - // TBD: throw a bad_alloc? - ErrorLog( "openGL error: out of memory; failed to create a geometry buffer" ); - delete_buffer(); - return; - } - m_buffercapacity = datasize; - } - // actual draw procedure starts here auto &chunkrecord { m_chunkrecords[ Geometry.chunk - 1 ] }; // sanity check; shouldn't be needed but, eh if( chunkrecord.size == 0 ) { return; } @@ -125,6 +89,47 @@ opengl_vbogeometrybank::release_() { delete_buffer(); } +void +opengl_vbogeometrybank::setup_buffer() { + + if( m_buffer != 0 ) { return; } + // if there's no buffer, we'll have to make one + // NOTE: this isn't exactly optimal in terms of ensuring the gfx card doesn't stall waiting for the data + // may be better to initiate upload earlier (during update phase) and trust this effort won't go to waste + if( true == m_chunks.empty() ) { return; } + + std::size_t datasize{ 0 }; + auto chunkiterator = m_chunks.cbegin(); + for( auto &chunkrecord : m_chunkrecords ) { + // fill records for all chunks, based on the chunk data + chunkrecord.is_good = false; // if we're re-creating buffer, chunks might've been uploaded in the old one + chunkrecord.offset = datasize; + chunkrecord.size = chunkiterator->vertices.size(); + datasize += chunkrecord.size; + ++chunkiterator; + } + // the odds for all created chunks to get replaced with empty ones are quite low, but the possibility does exist + if( datasize == 0 ) { return; } + // try to set up the buffer we need + ::glGenBuffers( 1, &m_buffer ); + bind_buffer(); + if( m_buffer == 0 ) { return; } // if we didn't get a buffer we'll try again during the next draw call + // NOTE: we're using static_draw since it's generally true for all we have implemented at the moment + // TODO: allow to specify usage hint at the object creation, and pass it here + ::glBufferData( + GL_ARRAY_BUFFER, + datasize * sizeof( gfx::basic_vertex ), + nullptr, + GL_STATIC_DRAW ); + if( ::glGetError() == GL_OUT_OF_MEMORY ) { + // TBD: throw a bad_alloc? + ErrorLog( "openGL error: out of memory; failed to create a geometry buffer" ); + delete_buffer(); + return; + } + m_buffercapacity = datasize; +} + void opengl_vbogeometrybank::bind_buffer() { diff --git a/openglgeometrybank.h b/openglgeometrybank.h index 5eee999b..2f0fae1e 100644 --- a/openglgeometrybank.h +++ b/openglgeometrybank.h @@ -53,6 +53,8 @@ private: // release() subclass details void release_() override; + void + setup_buffer(); void bind_buffer(); void diff --git a/openglprecipitation.cpp b/openglprecipitation.cpp index 68a71604..1e28de38 100644 --- a/openglprecipitation.cpp +++ b/openglprecipitation.cpp @@ -141,6 +141,8 @@ opengl_precipitation::update() { void opengl_precipitation::render() { + if( m_texture == null_handle ) { return; } + GfxRenderer->Bind_Texture( m_texture ); // cache entry state diff --git a/openglprecipitation.h b/openglprecipitation.h index 2b5fb994..3b49e7f1 100644 --- a/openglprecipitation.h +++ b/openglprecipitation.h @@ -39,6 +39,6 @@ private: GLuint m_uvbuffer { (GLuint)-1 }; GLuint m_indexbuffer { (GLuint)-1 }; GLint m_textureunit { 0 }; - texture_handle m_texture { -1 }; + texture_handle m_texture { null_handle }; float m_overcast { -1.f }; // cached overcast level, difference from current state triggers texture update }; diff --git a/openglrenderer.cpp b/openglrenderer.cpp index 5a6d9b61..a9a91bfb 100644 --- a/openglrenderer.cpp +++ b/openglrenderer.cpp @@ -27,6 +27,8 @@ http://mozilla.org/MPL/2.0/. int const EU07_PICKBUFFERSIZE { 1024 }; // size of (square) textures bound with the pick framebuffer int const EU07_ENVIRONMENTBUFFERSIZE { 256 }; // size of (square) environmental cube map texture +float const EU07_OPACITYDEFAULT { 0.5f }; + bool opengl_renderer::Init( GLFWwindow *Window ) { @@ -329,6 +331,7 @@ opengl_renderer::Render() { } // generate new frame m_renderpass.draw_mode = rendermode::none; // force setup anew + opengl_texture::reset_unit_cache(); m_debugtimestext.clear(); m_debugstats = debug_stats(); Render_pass( rendermode::color ); @@ -511,7 +514,7 @@ opengl_renderer::Render_pass( rendermode const Mode ) { setup_shadow_map( m_cabshadowtexture, m_cabshadowtexturematrix ); // cache shadow colour in case we need to account for cab light auto const shadowcolor{ m_shadowcolor }; - auto const *vehicle { simulation::Train->Dynamic() }; + auto *vehicle { simulation::Train->Dynamic() }; if( vehicle->InteriorLightLevel > 0.f ) { setup_shadow_color( glm::min( colors::white, shadowcolor + glm::vec4( vehicle->InteriorLight * vehicle->InteriorLightLevel, 1.f ) ) ); } @@ -525,6 +528,8 @@ opengl_renderer::Render_pass( rendermode const Mode ) { if( vehicle->InteriorLightLevel > 0.f ) { setup_shadow_color( shadowcolor ); } + // with the cab in place we can (finally) safely draw translucent part of the occupied vehicle + Render_Alpha( vehicle ); } if( m_environmentcubetexturesupport ) { @@ -982,7 +987,7 @@ opengl_renderer::setup_drawing( bool const Alpha ) { } else { ::glDisable( GL_BLEND ); - ::glAlphaFunc( GL_GREATER, 0.5f ); + ::glAlphaFunc( GL_GREATER, EU07_OPACITYDEFAULT ); } switch( m_renderpass.draw_mode ) { @@ -1536,14 +1541,14 @@ opengl_renderer::Insert( gfx::vertex_array &Vertices, gfx::geometrybank_handle c // replaces data of specified chunk with the supplied vertex data, starting from specified offset bool -opengl_renderer::Replace( gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, std::size_t const Offset ) { +opengl_renderer::Replace( gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, int const Type, std::size_t const Offset ) { return m_geometry.replace( Vertices, Geometry, Offset ); } // adds supplied vertex data at the end of specified chunk bool -opengl_renderer::Append( gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry ) { +opengl_renderer::Append( gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, int const Type ) { return m_geometry.append( Vertices, Geometry ); } @@ -1563,13 +1568,13 @@ opengl_renderer::Fetch_Material( std::string const &Filename, bool const Loadnow } void -opengl_renderer::Bind_Material( material_handle const Material ) { +opengl_renderer::Bind_Material( material_handle const Material, TSubModel *sm ) { auto const &material = m_materials.material( Material ); if( false == Global.BasicRenderer ) { - m_textures.bind( textureunit::normals, material.textures[1] ); + m_textures.bind( m_normaltextureunit - GL_TEXTURE0, material.textures[1] ); } - m_textures.bind( textureunit::diffuse, material.textures[0] ); + m_textures.bind( m_diffusetextureunit - GL_TEXTURE0, material.textures[0] ); } opengl_material const & @@ -1595,13 +1600,13 @@ opengl_renderer::select_unit( GLint const Textureunit ) { texture_handle opengl_renderer::Fetch_Texture( std::string const &Filename, bool const Loadnow, GLint format_hint ) { - return m_textures.create( Filename, Loadnow ); + return m_textures.create( Filename, Loadnow, GL_RGBA ); } void opengl_renderer::Bind_Texture( texture_handle const Texture ) { - m_textures.bind( textureunit::diffuse, Texture ); + m_textures.bind( m_diffusetextureunit - GL_TEXTURE0, Texture ); } void @@ -1623,13 +1628,13 @@ opengl_renderer::Texture( texture_handle const Texture ) const { } void -opengl_renderer::Pick_Control( std::function Callback ) { +opengl_renderer::Pick_Control_Callback( std::function Callback ) { m_control_pick_requests.emplace_back( Callback ); } void -opengl_renderer::Pick_Node( std::function Callback ) { +opengl_renderer::Pick_Node_Callback( std::function Callback ) { m_node_pick_requests.emplace_back( Callback ); } @@ -2449,15 +2454,14 @@ opengl_renderer::Render( TSubModel *Submodel ) { } #endif // material configuration: + auto const material{ ( + Submodel->m_material < 0 ? + Submodel->ReplacableSkinId[ -Submodel->m_material ] : // zmienialne skóry + Submodel->m_material ) }; // również 0 // textures... - if( Submodel->m_material < 0 ) { // zmienialne skóry - Bind_Material( Submodel->ReplacableSkinId[ -Submodel->m_material ] ); - } - else { - // również 0 - Bind_Material( Submodel->m_material ); - } - // ...colors... + Bind_Material( material ); + // ...colors and opacity... + auto const opacity { clamp( Material( material ).get_or_guess_opacity(), 0.f, 1.f ) }; if( Submodel->fVisible < 1.f ) { // setup ::glAlphaFunc( GL_GREATER, 0.f ); @@ -2470,6 +2474,10 @@ opengl_renderer::Render( TSubModel *Submodel ) { } else { ::glColor3fv( glm::value_ptr( Submodel->f4Diffuse ) ); // McZapkie-240702: zamiast ub + if( ( opacity != 0.f ) + && ( opacity != EU07_OPACITYDEFAULT ) ) { + ::glAlphaFunc( GL_GREATER, opacity ); + } } // ...specular... if( ( true == m_renderspecular ) && ( m_sunlight.specular.a > 0.01f ) ) { @@ -2505,9 +2513,13 @@ opengl_renderer::Render( TSubModel *Submodel ) { */ // post-draw reset if( Submodel->fVisible < 1.f ) { - ::glAlphaFunc( GL_GREATER, 0.5f ); + ::glAlphaFunc( GL_GREATER, EU07_OPACITYDEFAULT ); ::glDisable( GL_BLEND ); } + else if( ( opacity != 0.f ) + && ( opacity != EU07_OPACITYDEFAULT ) ) { + ::glAlphaFunc( GL_GREATER, EU07_OPACITYDEFAULT ); + } if( ( true == m_renderspecular ) && ( m_sunlight.specular.a > 0.01f ) ) { ::glMaterialfv( GL_FRONT, GL_SPECULAR, glm::value_ptr( colors::none ) ); } @@ -3104,6 +3116,11 @@ opengl_renderer::Render_Alpha( cell_sequence::reverse_iterator First, cell_seque // translucent parts of vehicles for( auto *path : cell->m_paths ) { for( auto *dynamic : path->Dynamics ) { + if( ( false == FreeFlyModeFlag ) + && ( simulation::Train->Dynamic() == dynamic ) ) { + // delay drawing translucent parts of occupied vehicle until after the cab was drawn + continue; + } Render_Alpha( dynamic ); } } @@ -3861,7 +3878,7 @@ opengl_renderer::Update( double const Deltatime ) { && ( true == FreeFlyModeFlag ) && ( ( true == DebugModeFlag ) || ( true == EditorModeFlag ) ) ) { - if( ( false == m_control_pick_requests.empty() ) + if( ( false == m_node_pick_requests.empty() ) || ( m_updateaccumulator >= 1.0 ) ) { Update_Pick_Node(); } diff --git a/openglrenderer.h b/openglrenderer.h index 523fd2b8..a0ccbc97 100644 --- a/openglrenderer.h +++ b/openglrenderer.h @@ -56,10 +56,10 @@ public: Insert( gfx::vertex_array &Vertices, gfx::geometrybank_handle const &Geometry, int const Type ) override; // replaces data of specified chunk with the supplied vertex data, starting from specified offset bool - Replace( gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, std::size_t const Offset = 0 ) override; + Replace( gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, int const Type, std::size_t const Offset = 0 ) override; // adds supplied vertex data at the end of specified chunk bool - Append( gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry ) override; + Append( gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, int const Type ) override; // provides direct access to vertex data of specfied chunk gfx::vertex_array const & Vertices( gfx::geometry_handle const &Geometry ) const override; @@ -67,7 +67,7 @@ public: material_handle Fetch_Material( std::string const &Filename, bool const Loadnow = true ) override; void - Bind_Material( material_handle const Material ) override; + Bind_Material( material_handle const Material, TSubModel *sm = nullptr ) override; opengl_material const & Material( material_handle const Material ) const override; // shader methods @@ -85,9 +85,9 @@ public: Texture( texture_handle const Texture ) const override; // utility methods void - Pick_Control( std::function Callback ) override; + Pick_Control_Callback( std::function Callback ) override; void - Pick_Node( std::function Callback ) override; + Pick_Node_Callback( std::function Callback ) override; TSubModel const * Pick_Control() const override { return m_pickcontrolitem; } scene::basic_node const * diff --git a/openglskydome.cpp b/openglskydome.cpp index 1a7d124c..78288cf3 100644 --- a/openglskydome.cpp +++ b/openglskydome.cpp @@ -11,7 +11,6 @@ http://mozilla.org/MPL/2.0/. #include "openglskydome.h" #include "simulationenvironment.h" -#include "openglgeometrybank.h" opengl_skydome::~opengl_skydome() { @@ -58,7 +57,14 @@ void opengl_skydome::update() { if( true == skydome.is_dirty() ) { if( ( m_coloursbuffer > 0 ) && ( m_coloursbuffer != (GLuint)-1 ) ) { ::glBindBuffer( GL_ARRAY_BUFFER, m_coloursbuffer ); - auto const &colors { skydome.colors() }; + auto &colors{ skydome.colors() }; + /* + float twilightfactor = clamp( -simulation::Environment.sun().getAngle(), 0.0f, 18.0f ) / 18.0f; + auto gamma = interpolate( glm::vec3( 0.45f ), glm::vec3( 1.0f ), twilightfactor ); + for( auto & color : colors ) { + color = glm::pow( color, gamma ); + } + */ ::glBufferSubData( GL_ARRAY_BUFFER, 0, colors.size() * sizeof( glm::vec3 ), colors.data() ); skydome.is_dirty() = false; } diff --git a/ref/imgui/imgui.h b/ref/imgui/imgui.h index 26c8887c..be275d27 100644 --- a/ref/imgui/imgui.h +++ b/ref/imgui/imgui.h @@ -598,6 +598,10 @@ namespace ImGui IMGUI_API void* MemAlloc(size_t size); IMGUI_API void MemFree(void* ptr); + // by zfedoran, https://github.com/ocornut/imgui/issues/1901 + IMGUI_API bool BufferingBar( const char* label, float value, const ImVec2& size_arg, const ImU32& bg_col, const ImU32& fg_col ); + IMGUI_API bool Spinner( const char* label, float radius, int thickness, const ImU32& color ); + } // namespace ImGui // Flags for ImGui::Begin() diff --git a/ref/imgui/imgui_widgets.cpp b/ref/imgui/imgui_widgets.cpp index 3319eb22..34ab7f5f 100644 --- a/ref/imgui/imgui_widgets.cpp +++ b/ref/imgui/imgui_widgets.cpp @@ -5728,3 +5728,91 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, } return false; } + +// by zfedoran, https://github.com/ocornut/imgui/issues/1901 + +bool ImGui::BufferingBar( const char* label, float value, const ImVec2& size_arg, const ImU32& bg_col, const ImU32& fg_col ) { + ImGuiWindow* window = GetCurrentWindow(); + if( window->SkipItems ) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID( label ); + + ImVec2 pos = window->DC.CursorPos; + ImVec2 size = size_arg; + size.x -= style.FramePadding.x * 2; + + const ImRect bb( pos, ImVec2( pos.x + size.x, pos.y + size.y ) ); + ItemSize( bb, style.FramePadding.y ); + if( !ItemAdd( bb, id ) ) + return false; + + // Render + const float circleStart = std::max( size.x * 0.7f, size.x - 30.f ); + const float circleEnd = size.x; + const float circleWidth = circleEnd - circleStart; + + window->DrawList->AddRectFilled( bb.Min, ImVec2( pos.x + circleStart, bb.Max.y ), bg_col ); + window->DrawList->AddRectFilled( bb.Min, ImVec2( pos.x + circleStart * value, bb.Max.y ), fg_col ); + + const float t = g.FrameCount * 0.1f; // g.Time; + const float r = size.y / 2; + const float speed = 1.5f; + + const float a = speed * 0; + const float b = speed * 0.333f; + const float c = speed * 0.666f; + + const float o1 = ( circleWidth + r ) * ( t + a - speed * (int)( ( t + a ) / speed ) ) / speed; + const float o2 = ( circleWidth + r ) * ( t + b - speed * (int)( ( t + b ) / speed ) ) / speed; + const float o3 = ( circleWidth + r ) * ( t + c - speed * (int)( ( t + c ) / speed ) ) / speed; + + window->DrawList->AddCircleFilled( ImVec2( pos.x + circleEnd - o1, bb.Min.y + r ), r, bg_col ); + window->DrawList->AddCircleFilled( ImVec2( pos.x + circleEnd - o2, bb.Min.y + r ), r, bg_col ); + window->DrawList->AddCircleFilled( ImVec2( pos.x + circleEnd - o3, bb.Min.y + r ), r, bg_col ); + + return true; +} + +bool ImGui::Spinner( const char* label, float radius, int thickness, const ImU32& color ) { + ImGuiWindow* window = GetCurrentWindow(); + if( window->SkipItems ) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID( label ); + + ImVec2 pos = window->DC.CursorPos; + ImVec2 size( ( radius ) * 2, ( radius + style.FramePadding.y ) * 2 ); + + const ImRect bb( pos, ImVec2( pos.x + size.x, pos.y + size.y ) ); + ItemSize( bb, style.FramePadding.y ); + if( !ItemAdd( bb, id ) ) + return false; + + // Render + window->DrawList->PathClear(); + + auto progress { g.FrameCount * 0.1f }; + + int num_segments = 30; + int start = abs( ImSin( progress * 1.8f )*( num_segments - 5 ) ); + + const float a_min = IM_PI * 2.0f * ( (float)start ) / (float)num_segments; + const float a_max = IM_PI * 2.0f * ( (float)num_segments - 3 ) / (float)num_segments; + + const ImVec2 centre = ImVec2( pos.x + radius, pos.y + radius + style.FramePadding.y ); + + for( int i = 0; i < num_segments; i++ ) { + const float a = a_min + ( (float)i / (float)num_segments ) * ( a_max - a_min ); + window->DrawList->PathLineTo( ImVec2( centre.x + ImCos( a + progress * 8 ) * radius, + centre.y + ImSin( a + progress * 8 ) * radius ) ); + } + + window->DrawList->PathStroke( color, false, thickness ); + + return true; +} diff --git a/renderer.h b/renderer.h index 0daaece1..4e08ffac 100644 --- a/renderer.h +++ b/renderer.h @@ -31,14 +31,14 @@ public: // creates a new geometry chunk of specified type from supplied vertex data, in specified bank. returns: handle to the chunk or NULL virtual auto Insert( gfx::vertex_array &Vertices, gfx::geometrybank_handle const &Geometry, int const Type ) -> gfx::geometry_handle = 0; // replaces data of specified chunk with the supplied vertex data, starting from specified offset - virtual auto Replace( gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, std::size_t const Offset = 0 ) -> bool = 0; + virtual auto Replace( gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, int const Type, std::size_t const Offset = 0 ) -> bool = 0; // adds supplied vertex data at the end of specified chunk - virtual auto Append( gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry ) -> bool = 0; + virtual auto Append( gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, int const Type ) -> bool = 0; // provides direct access to vertex data of specfied chunk virtual auto Vertices( gfx::geometry_handle const &Geometry ) const ->gfx::vertex_array const & = 0; // material methods virtual auto Fetch_Material( std::string const &Filename, bool const Loadnow = true ) -> material_handle = 0; - virtual void Bind_Material( material_handle const Material ) = 0; + virtual void Bind_Material( material_handle const Material, TSubModel *sm = nullptr ) = 0; virtual auto Material( material_handle const Material ) const -> opengl_material const & = 0; // shader methods virtual auto Fetch_Shader( std::string const &name ) -> std::shared_ptr = 0; @@ -49,8 +49,8 @@ public: virtual auto Texture( texture_handle const Texture ) -> opengl_texture & = 0; virtual auto Texture( texture_handle const Texture ) const -> opengl_texture const & = 0; // utility methods - virtual void Pick_Control( std::function Callback ) = 0; - virtual void Pick_Node( std::function Callback ) = 0; + virtual void Pick_Control_Callback( std::function Callback ) = 0; + virtual void Pick_Node_Callback( std::function Callback ) = 0; virtual auto Pick_Control() const -> TSubModel const * = 0; virtual auto Pick_Node() const -> scene::basic_node const * = 0; diff --git a/simulationenvironment.cpp b/simulationenvironment.cpp index d8fddb50..49e824ac 100644 --- a/simulationenvironment.cpp +++ b/simulationenvironment.cpp @@ -118,6 +118,7 @@ world_environment::update() { Global.DayLight.position = m_moon.getDirection(); Global.DayLight.direction = -1.0f * m_moon.getDirection(); keylightintensity = moonlightlevel; + m_lightintensity = 0.35f; // if the moon is up, it overrides the twilight twilightfactor = 0.0f; keylightcolor = glm::vec3( 255.0f / 255.0f, 242.0f / 255.0f, 202.0f / 255.0f ); @@ -128,6 +129,7 @@ world_environment::update() { Global.DayLight.position = m_sun.getDirection(); Global.DayLight.direction = -1.0f * m_sun.getDirection(); keylightintensity = sunlightlevel; + m_lightintensity = 1.0f; // include 'golden hour' effect in twilight lighting float const duskfactor = 1.0f - clamp( Global.SunAngle, 0.0f, 18.0f ) / 18.0f; keylightcolor = interpolate( diff --git a/simulationenvironment.h b/simulationenvironment.h index 009a4a3b..3c03c01e 100644 --- a/simulationenvironment.h +++ b/simulationenvironment.h @@ -39,6 +39,12 @@ public: // calculates current weather void compute_weather() const; // data access + inline auto const & + sun() const { + return m_sun; } + inline auto const & + light_intensity() const { + return m_lightintensity; } inline auto const & skydome() const { return m_skydome; } @@ -74,6 +80,7 @@ private: cStars m_stars; cSun m_sun; cMoon m_moon; + float m_lightintensity { 1.f }; TSky m_clouds; basic_precipitation m_precipitation; sound_source m_precipitationsound { sound_placement::external, -1 }; diff --git a/simulationstateserializer.cpp b/simulationstateserializer.cpp index 06b701cb..fbf441a9 100644 --- a/simulationstateserializer.cpp +++ b/simulationstateserializer.cpp @@ -118,7 +118,7 @@ state_serializer::deserialize( cParser &Input, scene::scratch_data &Scratchpad ) } timenow = std::chrono::steady_clock::now(); - if( std::chrono::duration_cast( timenow - timelast ).count() >= 200 ) { + if( std::chrono::duration_cast( timenow - timelast ).count() >= 50 ) { timelast = timenow; glfwPollEvents(); Application.set_progress( Input.getProgress(), Input.getFullProgress() ); diff --git a/skydome.cpp b/skydome.cpp index eae2147a..7af63d0a 100644 --- a/skydome.cpp +++ b/skydome.cpp @@ -3,6 +3,7 @@ #include "skydome.h" #include "color.h" #include "utilities.h" +#include "simulationenvironment.h" // sky gradient based on "A practical analytic model for daylight" // by A. J. Preetham Peter Shirley Brian Smits (University of Utah) @@ -189,6 +190,9 @@ float CSkyDome::PerezFunctionO2( float Perezcoeffs[ 5 ], const float Icostheta, void CSkyDome::RebuildColors() { + float twilightfactor = clamp( -simulation::Environment.sun().getAngle(), 0.0f, 18.0f ) / 18.0f; + auto gammacorrection = interpolate( glm::vec3( 0.45f ), glm::vec3( 1.0f ), twilightfactor ); + // get zenith luminance float const chi = ( (4.0f / 9.0f) - (m_turbidity / 120.0f) ) * ( M_PI - (2.0f * m_thetasun) ); float zenithluminance = ( (4.0453f * m_turbidity) - 4.9710f ) * std::tan( chi ) - (0.2155f * m_turbidity) + 2.4192f; @@ -260,6 +264,7 @@ void CSkyDome::RebuildColors() { colorconverter.z = 1.0f - std::exp( -m_expfactor * colorconverter.z ); } + colorconverter.y = clamp( colorconverter.y * 1.15f, 0.0f, 1.0f ); // desaturate sky colour, based on overcast level if( colorconverter.y > 0.0f ) { colorconverter.y *= ( 1.0f - m_overcast ); @@ -296,6 +301,11 @@ void CSkyDome::RebuildColors() { color.x = 0.20f * color.z; color.y = 0.65f * color.z; color = color * ( 1.15f - vertex.y ); // simple gradient, darkening towards the top + } + // gamma correction + color = glm::pow( color, gammacorrection ); + if( Global.GfxFramebufferSRGB ) { + color = glm::pow( color, glm::vec3( 2.2f ) - ( gammacorrection * 0.5f ) ); } // save m_colours[ i ] = color; diff --git a/skydome.h b/skydome.h index ad12765b..1dc0e006 100644 --- a/skydome.h +++ b/skydome.h @@ -26,7 +26,7 @@ public: std::vector const & vertices() const { return m_vertices; } - std::vector const & colors() const { + std::vector & colors() { return m_colours; } std::vector const & indices() const { return m_indices; } diff --git a/stdafx.h b/stdafx.h index d289f76c..e2b3607b 100644 --- a/stdafx.h +++ b/stdafx.h @@ -117,3 +117,5 @@ int const null_handle = 0; #else #include "imgui.h" #endif + +#pragma warning( disable : 5033 ) // "register is no longer a supported storage class" but it's all over python headers \ No newline at end of file diff --git a/sun.cpp b/sun.cpp index b2cce6a6..13e5c839 100644 --- a/sun.cpp +++ b/sun.cpp @@ -70,7 +70,7 @@ cSun::getDirection() { } float -cSun::getAngle() { +cSun::getAngle() const { return (float)m_body.elevref; } diff --git a/sun.h b/sun.h index 71c11094..a8faf05c 100644 --- a/sun.h +++ b/sun.h @@ -23,7 +23,7 @@ public: // returns vector pointing at the sun glm::vec3 getDirection(); // returns current elevation above horizon - float getAngle(); + float getAngle() const; // return current hour angle double getHourAngle() const; // returns current intensity of the sun diff --git a/uilayer.cpp b/uilayer.cpp index dc031ec0..723f1e5f 100644 --- a/uilayer.cpp +++ b/uilayer.cpp @@ -195,7 +195,7 @@ ui_layer::render() { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - +/* glPushAttrib( GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT ); // blendfunc included since 3rd party gui doesn't play nice glDisable( GL_LIGHTING ); glDisable( GL_DEPTH_TEST ); @@ -206,18 +206,10 @@ ui_layer::render() { ::glColor4fv( glm::value_ptr( colors::white ) ); // render code here - render_background(); render_texture(); - glDisable( GL_TEXTURE_2D ); - glDisable( GL_TEXTURE_CUBE_MAP ); - - render_progress(); - - glDisable( GL_BLEND ); - glPopAttrib(); - +*/ // imgui ui code ::glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT ); @@ -232,6 +224,8 @@ ui_layer::render() { ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); + render_background(); + render_progress(); render_panels(); render_tooltip(); // template method implementation @@ -265,7 +259,7 @@ void ui_layer::set_background( std::string const &Filename ) { if( false == Filename.empty() ) { - m_background = GfxRenderer->Fetch_Texture( Filename ); + m_background = GfxRenderer->Fetch_Texture( Filename, true, GL_RGBA ); } else { m_background = null_handle; @@ -283,52 +277,27 @@ ui_layer::set_background( std::string const &Filename ) { void ui_layer::render_progress() { - if( (m_progress == 0.0f) && (m_subtaskprogress == 0.0f) ) return; + if ((m_progress == 0.0f) && (m_subtaskprogress == 0.0f)) + return; - glm::vec2 origin, size; - if( m_progressbottom == true ) { - origin = glm::vec2{ 0.0f, 768.0f - 20.0f }; - size = glm::vec2{ 1024.0f, 20.0f }; - } - else { - origin = glm::vec2{ 75.0f, 640.0f }; - size = glm::vec2{ 320.0f, 16.0f }; - } + ImGui::SetNextWindowPos(ImVec2(50, Global.iWindowHeight - 50)); + ImGui::SetNextWindowSize(ImVec2(0, 0)); + ImGui::Begin( + "Loading", nullptr, + ImGuiWindowFlags_NoTitleBar + | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse + | ImGuiWindowFlags_NoCollapse ); - quad( glm::vec4( origin.x, origin.y, origin.x + size.x, origin.y + size.y ), glm::vec4(0.0f, 0.0f, 0.0f, 0.25f) ); - // secondary bar - if( m_subtaskprogress ) { - quad( - glm::vec4( origin.x, origin.y, origin.x + size.x * m_subtaskprogress, origin.y + size.y), - glm::vec4( 8.0f/255.0f, 160.0f/255.0f, 8.0f/255.0f, 0.35f ) ); - } - // primary bar - if( m_progress ) { - quad( - glm::vec4( origin.x, origin.y, origin.x + size.x * m_progress, origin.y + size.y ), - glm::vec4( 8.0f / 255.0f, 160.0f / 255.0f, 8.0f / 255.0f, 1.0f ) ); - } + const ImU32 col = ImGui::ColorConvertFloat4ToU32( ImVec4( 8.0f / 255.0f, 160.0f / 255.0f, 8.0f / 255.0f, 1.0f ) ); // ImGui::GetColorU32( ImGuiCol_ButtonHovered ); + const ImU32 bg = ImGui::ColorConvertFloat4ToU32( ImVec4( 8.0f / 255.0f, 160.0f / 255.0f, 8.0f / 255.0f, 0.35f ) ); // ImGui::GetColorU32( ImGuiCol_Button ); +/* + ImGui::Spinner( "##spinner", 8, 4, col ); + ImGui::SetCursorPos( ImVec2( 40, 18 ) ); +*/ + ImGui::SetCursorPos( ImVec2( 15, 15 ) ); + ImGui::BufferingBar( "##buffer_bar", m_progress, ImVec2( Global.iWindowWidth - 125, 4 ), bg, col ); - if( false == m_progresstext.empty() ) { - float const screenratio = static_cast( Global.iWindowWidth ) / Global.iWindowHeight; - float const width = - ( screenratio >= (4.0f/3.0f) ? - ( 4.0f / 3.0f ) * Global.iWindowHeight : - Global.iWindowWidth ); - float const heightratio = - ( screenratio >= ( 4.0f / 3.0f ) ? - Global.iWindowHeight / 768.f : - Global.iWindowHeight / 768.f * screenratio / ( 4.0f / 3.0f ) ); - float const height = 768.0f * heightratio; - - ::glColor4f( 216.0f / 255.0f, 216.0f / 255.0f, 216.0f / 255.0f, 1.0f ); - auto const charsize = 9.0f; - auto const textwidth = m_progresstext.size() * charsize; - auto const textheight = 12.0f; - ::glRasterPos2f( - ( 0.5f * ( Global.iWindowWidth - width ) + origin.x * heightratio ) + ( ( size.x * heightratio - textwidth ) * 0.5f * heightratio ), - ( 0.5f * ( Global.iWindowHeight - height ) + origin.y * heightratio ) + ( charsize ) + ( ( size.y * heightratio - textheight ) * 0.5f * heightratio ) ); - } + ImGui::End(); } void @@ -351,23 +320,22 @@ ui_layer::render_tooltip() { void ui_layer::render_background() { - if( m_background == 0 ) return; - // NOTE: we limit/expect the background to come with 4:3 ratio. - // TODO, TBD: if we expose texture width or ratio from texture object, this limitation could be lifted - GfxRenderer->Bind_Texture( m_background ); - auto const height { 768.0f }; - auto const &texture = GfxRenderer->Texture( m_background ); - float const width = ( - texture.width() == texture.height() ? - 1024.0f : // legacy mode, square texture displayed as 4:3 image - texture.width() / ( texture.height() / 768.0f ) ); - quad( - glm::vec4( - ( 1024.0f * 0.5f ) - ( width * 0.5f ), - ( 768.0f * 0.5f ) - ( height * 0.5f ), - ( 1024.0f * 0.5f ) - ( width * 0.5f ) + width, - ( 768.0f * 0.5f ) - ( height * 0.5f ) + height ), - colors::white ); + if (m_background == 0) + return; + + ImVec2 size = ImGui::GetIO().DisplaySize; + opengl_texture &tex = GfxRenderer->Texture(m_background); + tex.create(); + + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(size); + ImGui::Begin( + "Logo", nullptr, + ImGuiWindowFlags_NoTitleBar + | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse + | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus ); + ImGui::Image(reinterpret_cast(tex.id), size, ImVec2(0, 1), ImVec2(1, 0)); + ImGui::End(); } void diff --git a/uiwidgets.cpp b/uiwidgets.cpp new file mode 100644 index 00000000..18400d81 --- /dev/null +++ b/uiwidgets.cpp @@ -0,0 +1,104 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#include "stdafx.h" +#include "uiwidgets.h" + +#include "imgui_impl_glfw.h" +#ifdef EU07_USEIMGUIIMPLOPENGL2 +#include "imgui_impl_opengl2.h" +#else +#include "imgui_impl_opengl3.h" +#endif + +// by zfedoran, https://github.com/ocornut/imgui/issues/1901 + +namespace ImGui { + +bool BufferingBar( const char* label, float value, const ImVec2& size_arg, const ImU32& bg_col, const ImU32& fg_col ) { + ImGuiWindow* window = GetCurrentWindow(); + if( window->SkipItems ) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID( label ); + + ImVec2 pos = window->DC.CursorPos; + ImVec2 size = size_arg; + size.x -= style.FramePadding.x * 2; + + const ImRect bb( pos, ImVec2( pos.x + size.x, pos.y + size.y ) ); + ItemSize( bb, style.FramePadding.y ); + if( !ItemAdd( bb, id ) ) + return false; + + // Render + const float circleStart = size.x * 0.7f; + const float circleEnd = size.x; + const float circleWidth = circleEnd - circleStart; + + window->DrawList->AddRectFilled( bb.Min, ImVec2( pos.x + circleStart, bb.Max.y ), bg_col ); + window->DrawList->AddRectFilled( bb.Min, ImVec2( pos.x + circleStart * value, bb.Max.y ), fg_col ); + + const float t = g.Time; + const float r = size.y / 2; + const float speed = 1.5f; + + const float a = speed * 0; + const float b = speed * 0.333f; + const float c = speed * 0.666f; + + const float o1 = ( circleWidth + r ) * ( t + a - speed * (int)( ( t + a ) / speed ) ) / speed; + const float o2 = ( circleWidth + r ) * ( t + b - speed * (int)( ( t + b ) / speed ) ) / speed; + const float o3 = ( circleWidth + r ) * ( t + c - speed * (int)( ( t + c ) / speed ) ) / speed; + + window->DrawList->AddCircleFilled( ImVec2( pos.x + circleEnd - o1, bb.Min.y + r ), r, bg_col ); + window->DrawList->AddCircleFilled( ImVec2( pos.x + circleEnd - o2, bb.Min.y + r ), r, bg_col ); + window->DrawList->AddCircleFilled( ImVec2( pos.x + circleEnd - o3, bb.Min.y + r ), r, bg_col ); +} + +bool Spinner( const char* label, float radius, int thickness, const ImU32& color ) { + ImGuiWindow* window = GetCurrentWindow(); + if( window->SkipItems ) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID( label ); + + ImVec2 pos = window->DC.CursorPos; + ImVec2 size( ( radius ) * 2, ( radius + style.FramePadding.y ) * 2 ); + + const ImRect bb( pos, ImVec2( pos.x + size.x, pos.y + size.y ) ); + ItemSize( bb, style.FramePadding.y ); + if( !ItemAdd( bb, id ) ) + return false; + + // Render + window->DrawList->PathClear(); + + int num_segments = 30; + int start = abs( ImSin( g.Time*1.8f )*( num_segments - 5 ) ); + + const float a_min = IM_PI * 2.0f * ( (float)start ) / (float)num_segments; + const float a_max = IM_PI * 2.0f * ( (float)num_segments - 3 ) / (float)num_segments; + + const ImVec2 centre = ImVec2( pos.x + radius, pos.y + radius + style.FramePadding.y ); + + for( int i = 0; i < num_segments; i++ ) { + const float a = a_min + ( (float)i / (float)num_segments ) * ( a_max - a_min ); + window->DrawList->PathLineTo( ImVec2( centre.x + ImCos( a + g.Time * 8 ) * radius, + centre.y + ImSin( a + g.Time * 8 ) * radius ) ); + } + + window->DrawList->PathStroke( color, false, thickness ); +} + +} // namespace ImGui diff --git a/uiwidgets.h b/uiwidgets.h new file mode 100644 index 00000000..4d4862fd --- /dev/null +++ b/uiwidgets.h @@ -0,0 +1,17 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +namespace ImGui { + +bool BufferingBar( const char* label, float value, const ImVec2& size_arg, const ImU32& bg_col, const ImU32& fg_col ); +bool Spinner( const char* label, float radius, int thickness, const ImU32& color ); + +} // namespace ImGui diff --git a/utilities.cpp b/utilities.cpp index 620fb24f..46032dda 100644 --- a/utilities.cpp +++ b/utilities.cpp @@ -487,3 +487,13 @@ deserialize_random_set( cParser &Input, char const *Break ) { return ""; } } + +int count_trailing_zeros( uint32_t val ) +{ + int r = 0; + + for( uint32_t shift = 1; !( val & shift ); shift <<= 1 ) + r++; + + return r; +} diff --git a/utilities.h b/utilities.h index 8b7f99c5..202601b2 100644 --- a/utilities.h +++ b/utilities.h @@ -334,6 +334,8 @@ glm::dvec3 LoadPoint( class cParser &Input ); std::string deserialize_random_set( cParser &Input, char const *Break = "\n\r\t ;" ); +int count_trailing_zeros( uint32_t val ); + namespace threading { // simple POD pairing of a data item and a mutex