From 0fd23a53d9dcbefb235de2816db6baa05ff1fa52 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Sat, 26 Oct 2019 15:17:09 +0200 Subject: [PATCH] milek7/sim branch opengl 3.3 renderer import --- AnimModel.h | 1 + Classes.h | 1 + DynObj.cpp | 46 +- DynObj.h | 7 +- Globals.cpp | 4 +- Globals.h | 20 +- Logs.h | 6 +- McZapkie/Mover.cpp | 4 +- Model3d.cpp | 123 +- Model3d.h | 10 +- PyInt.cpp | 2 +- Texture.cpp | 131 +- Texture.h | 30 +- Timer.h | 2 + Track.cpp | 4 +- Track.h | 1 + Traction.h | 1 + Train.cpp | 2 +- application.cpp | 52 +- application.h | 2 + drivermouseinput.cpp | 163 +- drivermouseinput.h | 3 +- editormode.cpp | 13 +- editoruipanels.h | 14 + geometrybank.cpp | 107 + geometrybank.h | 10 +- gl/bindable.h | 27 + gl/buffer.cpp | 89 + gl/buffer.h | 46 + gl/cubemap.cpp | 38 + gl/cubemap.h | 19 + gl/fence.cpp | 20 + gl/fence.h | 20 + gl/framebuffer.cpp | 103 + gl/framebuffer.h | 33 + gl/glsl_common.cpp | 72 + gl/glsl_common.h | 10 + gl/object.h | 32 + gl/pbo.cpp | 65 + gl/pbo.h | 22 + gl/postfx.cpp | 49 + gl/postfx.h | 25 + gl/query.cpp | 65 + gl/query.h | 36 + gl/renderbuffer.cpp | 27 + gl/renderbuffer.h | 19 + gl/shader.cpp | 334 ++ gl/shader.h | 96 + gl/ubo.cpp | 19 + gl/ubo.h | 113 + gl/vao.cpp | 85 + gl/vao.h | 37 + maszyna.vcxproj.filters | 229 +- material.cpp | 283 +- material.h | 52 +- moon.h | 2 - opengl33light.cpp | 22 + opengl33light.h | 28 + opengl33particles.cpp | 138 + opengl33particles.h | 54 + opengl33precipitation.cpp | 159 + opengl33precipitation.h | 43 + opengl33renderer.cpp | 3904 +++++++++++++++++++++ opengl33renderer.h | 377 ++ openglcamera.h | 5 +- openglgeometrybank.cpp | 4 +- opengllight.h | 1 - openglmatrixstack.h | 4 - openglparticles.h | 2 - openglprecipitation.cpp | 171 + openglprecipitation.h | 44 + openglrenderer.cpp | 116 +- openglrenderer.h | 25 +- openglskydome.cpp | 6 +- openglskydome.h | 4 +- precipitation.cpp | 131 +- precipitation.h | 21 +- ref/imgui/examples/imgui_impl_opengl2.cpp | 2 +- ref/imgui/examples/imgui_impl_opengl3.cpp | 2 +- renderer.h | 13 +- scene.h | 6 + scenenode.cpp | 4 +- simulationenvironment.h | 5 + sky.h | 1 + stars.h | 1 + stdafx.h | 38 +- sun.h | 2 - utilities.cpp | 6 + utilities.h | 11 + version.h | 2 +- 90 files changed, 7629 insertions(+), 549 deletions(-) create mode 100644 gl/bindable.h create mode 100644 gl/buffer.cpp create mode 100644 gl/buffer.h create mode 100644 gl/cubemap.cpp create mode 100644 gl/cubemap.h create mode 100644 gl/fence.cpp create mode 100644 gl/fence.h create mode 100644 gl/framebuffer.cpp create mode 100644 gl/framebuffer.h create mode 100644 gl/glsl_common.cpp create mode 100644 gl/glsl_common.h create mode 100644 gl/object.h create mode 100644 gl/pbo.cpp create mode 100644 gl/pbo.h create mode 100644 gl/postfx.cpp create mode 100644 gl/postfx.h create mode 100644 gl/query.cpp create mode 100644 gl/query.h create mode 100644 gl/renderbuffer.cpp create mode 100644 gl/renderbuffer.h create mode 100644 gl/shader.cpp create mode 100644 gl/shader.h create mode 100644 gl/ubo.cpp create mode 100644 gl/ubo.h create mode 100644 gl/vao.cpp create mode 100644 gl/vao.h create mode 100644 opengl33light.cpp create mode 100644 opengl33light.h create mode 100644 opengl33particles.cpp create mode 100644 opengl33particles.h create mode 100644 opengl33precipitation.cpp create mode 100644 opengl33precipitation.h create mode 100644 opengl33renderer.cpp create mode 100644 opengl33renderer.h create mode 100644 openglprecipitation.cpp create mode 100644 openglprecipitation.h diff --git a/AnimModel.h b/AnimModel.h index 244d26f3..9f8d34c5 100644 --- a/AnimModel.h +++ b/AnimModel.h @@ -125,6 +125,7 @@ class TAnimAdvanced class TAnimModel : public scene::basic_node { friend opengl_renderer; + friend opengl33_renderer; friend itemproperties_panel; public: diff --git a/Classes.h b/Classes.h index a01470cc..3e0bc8e6 100644 --- a/Classes.h +++ b/Classes.h @@ -13,6 +13,7 @@ http://mozilla.org/MPL/2.0/. // Ra: zestaw klas do robienia wskaźników, aby uporządkować nagłówki //--------------------------------------------------------------------------- class opengl_renderer; +class opengl33_renderer; class TTrack; // odcinek trajektorii class basic_event; class TTrain; // pojazd sterowany diff --git a/DynObj.cpp b/DynObj.cpp index d001dcf8..85d69a74 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -204,18 +204,18 @@ material_data::assign( std::string const &Replacableskin ) { } textures_alpha = ( - GfxRenderer->Material( replacable_skins[ 1 ] ).has_alpha ? + GfxRenderer->Material( replacable_skins[ 1 ] ).is_translucent() ? 0x31310031 : // tekstura -1 z kanałem alfa - nie renderować w cyklu nieprzezroczystych 0x30300030 ); // wszystkie tekstury nieprzezroczyste - nie renderować w cyklu przezroczystych - if( GfxRenderer->Material( replacable_skins[ 2 ] ).has_alpha ) { + if( GfxRenderer->Material( replacable_skins[ 2 ] ).is_translucent() ) { // tekstura -2 z kanałem alfa - nie renderować w cyklu nieprzezroczystych textures_alpha |= 0x02020002; } - if( GfxRenderer->Material( replacable_skins[ 3 ] ).has_alpha ) { + if( GfxRenderer->Material( replacable_skins[ 3 ] ).is_translucent() ) { // tekstura -3 z kanałem alfa - nie renderować w cyklu nieprzezroczystych textures_alpha |= 0x04040004; } - if( GfxRenderer->Material( replacable_skins[ 4 ] ).has_alpha ) { + if( GfxRenderer->Material( replacable_skins[ 4 ] ).is_translucent() ) { // tekstura -4 z kanałem alfa - nie renderować w cyklu nieprzezroczystych textures_alpha |= 0x08080008; } @@ -477,12 +477,9 @@ void TDynamicObject::SetPneumatic(bool front, bool red) void TDynamicObject::UpdateAxle(TAnim *pAnim) { // animacja osi - pAnim->smAnimated->SetRotate(float3(1, 0, 0), *pAnim->dWheelAngle); -}; - -void TDynamicObject::UpdateBoogie(TAnim *pAnim) -{ // animacja wózka - pAnim->smAnimated->SetRotate(float3(1, 0, 0), *pAnim->dWheelAngle); + size_t wheel_id = pAnim->dWheelAngle; + pAnim->smAnimated->SetRotate(float3(1, 0, 0), dWheelAngle[wheel_id]); + pAnim->smAnimated->future_transform = glm::rotate((float)glm::radians(m_future_wheels_angle[wheel_id]), glm::vec3(1.0f, 0.0f, 0.0f)); }; // animacja drzwi - przesuw @@ -3210,7 +3207,11 @@ bool TDynamicObject::Update(double dt, double dt1) // TBD: place the meter on mover logic level? simulation::Train->add_distance( dDOMoveLen ); } + glm::dvec3 old_pos = vPosition; Move(dDOMoveLen); + + m_future_movement = (glm::dvec3(vPosition) - old_pos) / dt1 * Timer::GetDeltaRenderTime(); + if (!bEnabled) // usuwane pojazdy nie mają toru { // pojazd do usunięcia bDynamicRemove = true; // sprawdzić @@ -3316,17 +3317,19 @@ bool TDynamicObject::Update(double dt, double dt1) if (MoverParameters->Vel != 0) { // McZapkie-050402: krecenie kolami: + glm::dvec3 old_wheels = glm::dvec3(dWheelAngle[0], dWheelAngle[1], dWheelAngle[2]); + dWheelAngle[0] += 114.59155902616464175359630962821 * MoverParameters->V * dt1 / MoverParameters->WheelDiameterL; // przednie toczne dWheelAngle[1] += MoverParameters->nrot * dt1 * 360.0; // napędne dWheelAngle[2] += 114.59155902616464175359630962821 * MoverParameters->V * dt1 / MoverParameters->WheelDiameterT; // tylne toczne - if (dWheelAngle[0] > 360.0) - dWheelAngle[0] -= 360.0; // a w drugą stronę jak się kręcą? - if (dWheelAngle[1] > 360.0) - dWheelAngle[1] -= 360.0; - if (dWheelAngle[2] > 360.0) - dWheelAngle[2] -= 360.0; + + m_future_wheels_angle = (glm::dvec3(dWheelAngle[0], dWheelAngle[1], dWheelAngle[2]) - old_wheels) / dt1 * Timer::GetDeltaRenderTime(); + + dWheelAngle[0] = clamp_circular( dWheelAngle[0] ); + dWheelAngle[1] = clamp_circular( dWheelAngle[1] ); + dWheelAngle[2] = clamp_circular( dWheelAngle[2] ); } if (pants) // pantograf może być w wagonie kuchennym albo pojeździe rewizyjnym (np. SR61) { // przeliczanie kątów dla pantografów @@ -4530,7 +4533,7 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co } // Ra: ustawianie indeksów osi for (i = 0; i < iAnimType[ANIM_WHEELS]; ++i) // ilość osi (zabezpieczenie przed błędami w CHK) - pAnimations[i].dWheelAngle = dWheelAngle + 1; // domyślnie wskaźnik na napędzające + pAnimations[i].dWheelAngle = 1; // domyślnie wskaźnik na napędzające i = 0; j = 1; k = 0; @@ -4543,13 +4546,13 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co { // wersja ze wskaźnikami jest bardziej elastyczna na nietypowe układy if ((k >= 'A') && (k <= 'J')) // 10 chyba maksimum? { - pAnimations[i++].dWheelAngle = dWheelAngle + 1; // obrót osi napędzających + pAnimations[i++].dWheelAngle = 1; // obrót osi napędzających --k; // następna będzie albo taka sama, albo bierzemy kolejny znak m = 2; // następujące toczne będą miały inną średnicę } else if ((k >= '1') && (k <= '9')) { - pAnimations[i++].dWheelAngle = dWheelAngle + m; // obrót osi tocznych + pAnimations[i++].dWheelAngle = m; // obrót osi tocznych --k; // następna będzie albo taka sama, albo bierzemy kolejny znak } else @@ -6381,6 +6384,11 @@ void TDynamicObject::OverheadTrack(float o) } }; +glm::dvec3 TDynamicObject::get_future_movement() const { + + return m_future_movement; +} + // returns type of the nearest functional power source present in the trainset TPowerSource TDynamicObject::ConnectedEnginePowerSource( TDynamicObject const *Caller ) const { diff --git a/DynObj.h b/DynObj.h index 55a4ba15..ad41c53e 100644 --- a/DynObj.h +++ b/DynObj.h @@ -124,7 +124,7 @@ public: union { // parametry animacji TAnimValveGear *pValveGear; // współczynniki do animacji parowozu - double *dWheelAngle; // wskaźnik na kąt obrotu osi + int dWheelAngle; // wskaźnik na kąt obrotu osi float *fParam; // różne parametry dla animacji TAnimPant *fParamPants; // różne parametry dla animacji }; @@ -162,6 +162,7 @@ struct material_data { class TDynamicObject { // klasa pojazdu friend opengl_renderer; + friend opengl33_renderer; public: static bool bDynamicRemove; // moved from ground @@ -179,6 +180,8 @@ private: // położenie pojazdu w świecie oraz parametry ruchu float fAxleDist; // rozstaw wózków albo osi do liczenia proporcji zacienienia Math3D::vector3 modelRot; // obrot pudła względem świata - do przeanalizowania, czy potrzebne!!! TDynamicObject * ABuFindNearestObject( TTrack *Track, TDynamicObject *MyPointer, int &CouplNr ); + glm::dvec3 m_future_movement; + glm::dvec3 m_future_wheels_angle; public: // parametry położenia pojazdu dostępne publicznie @@ -257,7 +260,6 @@ private: void UpdateNone(TAnim *pAnim){}; // animacja pusta (funkcje ustawiania submodeli, gdy blisko kamery) */ void UpdateAxle(TAnim *pAnim); // animacja osi - void UpdateBoogie(TAnim *pAnim); // animacja wózka void UpdateDoorTranslate(TAnim *pAnim); // animacja drzwi - przesuw void UpdateDoorRotate(TAnim *pAnim); // animacja drzwi - obrót void UpdateDoorFold(TAnim *pAnim); // animacja drzwi - składanie @@ -655,6 +657,7 @@ private: void DestinationSet(std::string to, std::string numer); material_handle DestinationFind( std::string Destination ); void OverheadTrack(float o); + glm::dvec3 get_future_movement() const; double MED[9][8]; // lista zmiennych do debugowania hamulca ED static std::string const MED_labels[ 8 ]; diff --git a/Globals.cpp b/Globals.cpp index b77a9249..6dac0e27 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -404,9 +404,9 @@ global_settings::ConfigParse(cParser &Parser) { else if( token == "gfx.reflections.framerate" ) { auto const updatespersecond { std::abs( Parser.getToken() ) }; - ReflectionUpdatesPerSecond = ( + ReflectionUpdateInterval = ( updatespersecond > 0 ? - 1000 / std::min( 30.0, updatespersecond ) : + 1.0 / std::min( 30.0, updatespersecond ) : 0 ); } else if (token == "timespeed") diff --git a/Globals.h b/Globals.h index 695b06bc..75a89b6e 100644 --- a/Globals.h +++ b/Globals.h @@ -25,6 +25,7 @@ struct global_settings { bool ctrlState{ false }; bool altState{ false }; std::mt19937 random_engine{ std::mt19937( static_cast( std::time( NULL ) ) ) }; + std::mt19937 local_random_engine{ std::mt19937( static_cast( std::time( NULL ) ) ) }; TDynamicObject *changeDynObj{ nullptr };// info o zmianie pojazdu TCamera pCamera; // parametry kamery TCamera pDebugCamera; @@ -91,7 +92,6 @@ struct global_settings { // ui int PythonScreenUpdateRate { 200 }; // delay between python-based screen updates, in milliseconds int iTextMode{ 0 }; // tryb pracy wyświetlacza tekstowego - int iScreenMode[ 12 ] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // numer ekranu wyświetlacza tekstowego glm::vec4 UITextColor { glm::vec4( 225.f / 255.f, 225.f / 255.f, 225.f / 255.f, 1.f ) }; // base color of UI text float UIBgOpacity { 0.65f }; // opacity of ui windows std::string asLang{ "pl" }; // domyślny język - http://tools.ietf.org/html/bcp47 @@ -99,6 +99,7 @@ struct global_settings { int iWindowWidth{ 800 }; int iWindowHeight{ 600 }; float fDistanceFactor{ iWindowHeight / 768.f }; // baza do przeliczania odległości dla LoD + float targetfps { 0.0f }; bool bFullScreen{ false }; bool VSync{ false }; bool bWireFrame{ false }; @@ -115,7 +116,7 @@ struct global_settings { float depth{ 250.f }; float distance{ 500.f }; // no longer used } shadowtune; - int ReflectionUpdatesPerSecond{ static_cast( 1000 / ( 1.0 / 300.0 ) ) }; + double ReflectionUpdateInterval{ 300.0 }; bool bUseVBO{ true }; // czy jest VBO w karcie graficznej (czy użyć) float AnisotropicFiltering{ 8.f }; // requested level of anisotropic filtering. TODO: move it to renderer object float FieldOfView{ 45.f }; // vertical field of view for the camera. TODO: move it to the renderer @@ -175,6 +176,21 @@ struct global_settings { // other std::string AppName{ "EU07" }; std::string asVersion{ "UNKNOWN" }; // z opisem + // TODO: move these to relevant areas + bool render_cab = true; + int gfx_framebuffer_width = -1; + int gfx_framebuffer_height = -1; + bool gfx_shadowmap_enabled = true; + bool gfx_envmap_enabled = true; + bool gfx_postfx_motionblur_enabled = true; + float gfx_postfx_motionblur_shutter = 0.01f; + GLenum gfx_postfx_motionblur_format = GL_RG16F; + GLenum gfx_format_color = GL_RGB16F; + GLenum gfx_format_depth = GL_DEPTH_COMPONENT32F; + bool gfx_skippipeline = false; + bool gfx_extraeffects = true; + bool gfx_shadergamma = false; + bool gfx_usegles = false; // methods void LoadIniFile( std::string asFileName ); diff --git a/Logs.h b/Logs.h index dee95e5f..0d41bffb 100644 --- a/Logs.h +++ b/Logs.h @@ -16,7 +16,11 @@ enum logtype : unsigned int { generic = ( 1 << 0 ), file = ( 1 << 1 ), model = ( 1 << 2 ), - texture = ( 1 << 3 ) + texture = ( 1 << 3 ), +// lua = ( 1 << 4 ), + material = ( 1 << 5 ), + shader = ( 1 << 6 ), +// net = ( 1 << 7 ) }; void WriteLog( const char *str, logtype const Type = logtype::generic ); diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index c2b53845..82860deb 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -8953,8 +8953,8 @@ void TMoverParameters::LoadFIZ_Param( std::string const &line ) { auto lookup = categories.find( category ); CategoryFlag = ( lookup != categories.end() ? - lookup->second : - 0 ); + lookup->second : + 0 ); if( CategoryFlag == 0 ) { ErrorLog( "Unknown vehicle category: \"" + category + "\"." ); } diff --git a/Model3d.cpp b/Model3d.cpp index 85dd480f..fcfa5dc6 100644 --- a/Model3d.cpp +++ b/Model3d.cpp @@ -374,17 +374,34 @@ 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 - // 2. tekstura ma przezroczystość - iFlags |= - ( ( ( Opacity < 1.0 ) - && ( ( m_material != null_handle ) - && ( GfxRenderer->Material( m_material ).has_alpha ) ) ) ? + iFlags |= ( + Opacity < 0.999f ? 0x20 : - 0x10 ); // 0x10-nieprzezroczysta, 0x20-przezroczysta + 0x10 ); // 0x20-przezroczysta, 0x10-nieprzezroczysta }; } - else + else { iFlags |= 0x10; + } + + 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)) + { + iFlags &= ~0x30; + if (mat.opacity == 0.0f) + iFlags |= 0x20; // translucent + else + iFlags |= 0x10; // opaque + } + + // and same thing with selfillum + if (!std::isnan(mat.selfillum)) + fLight = mat.selfillum; + } // visibility range std::string discard; @@ -974,73 +991,75 @@ TSubModel *TSubModel::GetFromName(std::string const &search, bool i) // WORD hbIndices[18]={3,0,1,5,4,2,1,0,4,1,5,3,2,3,5,2,4,0}; void TSubModel::RaAnimation(TAnimType a) +{ + glm::mat4 m = OpenGLMatrices.data(GL_MODELVIEW); + RaAnimation(m, a); + glLoadMatrixf(glm::value_ptr(m)); +} + +void TSubModel::RaAnimation(glm::mat4 &m, TAnimType a) { // wykonanie animacji niezależnie od renderowania switch (a) { // korekcja położenia, jeśli submodel jest animowany case TAnimType::at_Translate: // Ra: było "true" if (iAnimOwner != iInstance) break; // cudza animacja - glTranslatef(v_TransVector.x, v_TransVector.y, v_TransVector.z); + m = glm::translate(m, glm::vec3(v_TransVector.x, v_TransVector.y, v_TransVector.z)); break; case TAnimType::at_Rotate: // Ra: było "true" if (iAnimOwner != iInstance) break; // cudza animacja - glRotatef(f_Angle, v_RotateAxis.x, v_RotateAxis.y, v_RotateAxis.z); + m = glm::rotate(m, glm::radians(f_Angle), glm::vec3(v_RotateAxis.x, v_RotateAxis.y, v_RotateAxis.z)); break; case TAnimType::at_RotateXYZ: if (iAnimOwner != iInstance) break; // cudza animacja - glTranslatef(v_TransVector.x, v_TransVector.y, v_TransVector.z); - glRotatef(v_Angles.x, 1.0f, 0.0f, 0.0f); - glRotatef(v_Angles.y, 0.0f, 1.0f, 0.0f); - glRotatef(v_Angles.z, 0.0f, 0.0f, 1.0f); + m = glm::translate(m, glm::vec3(v_TransVector.x, v_TransVector.y, v_TransVector.z)); + m = glm::rotate(m, glm::radians(v_Angles.x), glm::vec3(1.0f, 0.0f, 0.0f)); + m = glm::rotate(m, glm::radians(v_Angles.y), glm::vec3(0.0f, 1.0f, 0.0f)); + m = glm::rotate(m, glm::radians(v_Angles.z), glm::vec3(0.0f, 0.0f, 1.0f)); break; case TAnimType::at_SecondsJump: // sekundy z przeskokiem - glRotatef(simulation::Time.data().wSecond * 6.0, 0.0, 1.0, 0.0); + m = glm::rotate(m, glm::radians(simulation::Time.data().wSecond * 6.0f), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_MinutesJump: // minuty z przeskokiem - glRotatef(simulation::Time.data().wMinute * 6.0, 0.0, 1.0, 0.0); + m = glm::rotate(m, glm::radians(simulation::Time.data().wMinute * 6.0f), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_HoursJump: // godziny skokowo 12h/360° - glRotatef(simulation::Time.data().wHour * 30.0 * 0.5, 0.0, 1.0, 0.0); + m = glm::rotate(m, glm::radians(simulation::Time.data().wHour * 30.0f * 0.5f), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_Hours24Jump: // godziny skokowo 24h/360° - glRotatef(simulation::Time.data().wHour * 15.0 * 0.25, 0.0, 1.0, 0.0); + m = glm::rotate(m, glm::radians(simulation::Time.data().wHour * 15.0f * 0.25f), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_Seconds: // sekundy płynnie - glRotatef(simulation::Time.second() * 6.0, 0.0, 1.0, 0.0); + m = glm::rotate(m, glm::radians((float)simulation::Time.second() * 6.0f), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_Minutes: // minuty płynnie - glRotatef(simulation::Time.data().wMinute * 6.0 + simulation::Time.second() * 0.1, 0.0, 1.0, 0.0); + m = glm::rotate(m, glm::radians(simulation::Time.data().wMinute * 6.0f + (float)simulation::Time.second() * 0.1f), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_Hours: // godziny płynnie 12h/360° - glRotatef(2.0 * Global.fTimeAngleDeg, 0.0, 1.0, 0.0); + // glRotatef(GlobalTime->hh*30.0+GlobalTime->mm*0.5+GlobalTime->mr/120.0,0.0,1.0,0.0); + m = glm::rotate(m, glm::radians(2.0f * (float)Global.fTimeAngleDeg), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_Hours24: // godziny płynnie 24h/360° - glRotatef(Global.fTimeAngleDeg, 0.0, 1.0, 0.0); + // glRotatef(GlobalTime->hh*15.0+GlobalTime->mm*0.25+GlobalTime->mr/240.0,0.0,1.0,0.0); + m = glm::rotate(m, glm::radians((float)Global.fTimeAngleDeg), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_Billboard: // obrót w pionie do kamery { Math3D::matrix4x4 mat; mat.OpenGL_Matrix( OpenGLMatrices.data_array( GL_MODELVIEW ) ); float3 gdzie = float3(mat[3][0], mat[3][1], mat[3][2]); // początek układu współrzędnych submodelu względem kamery - glLoadIdentity(); // macierz jedynkowa - glTranslatef(gdzie.x, gdzie.y, gdzie.z); // początek układu zostaje bez - // zmian - glRotated(atan2(gdzie.x, gdzie.z) * 180.0 / M_PI, 0.0, 1.0, - 0.0); // jedynie obracamy w pionie o kąt + m = glm::mat4(1.0f); + m = glm::translate(m, glm::vec3(gdzie.x, gdzie.y, gdzie.z)); // początek układu zostaje bez zmian + m = glm::rotate(m, (float)atan2(gdzie.x, gdzie.z), glm::vec3(0.0f, 1.0f, 0.0f)); // jedynie obracamy w pionie o kąt } break; case TAnimType::at_Wind: // ruch pod wpływem wiatru (wiatr będziemy liczyć potem...) - glRotated(1.5 * std::sin(M_PI * simulation::Time.second() / 6.0), 0.0, 1.0, 0.0); + m = glm::rotate(m, glm::radians(1.5f * (float)sin(M_PI * simulation::Time.second() / 6.0)), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_Sky: // animacja nieba - glRotated(Global.fLatitudeDeg, 1.0, 0.0, 0.0); // ustawienie osi OY na północ - // glRotatef(Global.fTimeAngleDeg,0.0,1.0,0.0); //obrót dobowy osi OX - glRotated(-fmod(Global.fTimeAngleDeg, 360.0), 0.0, 1.0, 0.0); // obrót dobowy osi OX - break; - case TAnimType::at_IK11: // ostatni element animacji szkieletowej (podudzie, stopa) - glRotatef(v_Angles.z, 0.0f, 1.0f, 0.0f); // obrót względem osi pionowej (azymut) - glRotatef(v_Angles.x, 1.0f, 0.0f, 0.0f); // obrót względem poziomu (deklinacja) + m = glm::rotate(m, glm::radians((float)Global.fLatitudeDeg), glm::vec3(0.0f, 1.0f, 0.0f)); // ustawienie osi OY na północ + m = glm::rotate(m, glm::radians((float)-fmod(Global.fTimeAngleDeg, 360.0)), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_DigiClk: // animacja zegara cyfrowego { // ustawienie animacji w submodelach potomnych @@ -1063,8 +1082,8 @@ void TSubModel::RaAnimation(TAnimType a) } if (mAnimMatrix) // można by to dać np. do at_Translate { - glMultMatrixf(mAnimMatrix->readArray()); - mAnimMatrix = NULL; // jak animator będzie potrzebował, to ustawi ponownie + m *= glm::make_mat4(mAnimMatrix->e); + mAnimMatrix = nullptr; // jak animator będzie potrzebował, to ustawi ponownie } }; @@ -1783,13 +1802,31 @@ void TSubModel::BinInit(TSubModel *s, float4x4 *m, std::vector *t, } */ m_material = GfxRenderer->Fetch_Material( m_materialname ); - if( ( iFlags & 0x30 ) == 0 ) { - // texture-alpha based fallback if for some reason we don't have opacity flag set yet - iFlags |= ( - ( ( m_material != null_handle ) - && ( GfxRenderer->Material( m_material ).has_alpha ) ) ? - 0x20 : - 0x10 ); // 0x10-nieprzezroczysta, 0x20-przezroczysta + + // 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 + else + iFlags |= 0x10; // opaque + } + + if (m_material > 0) + { + opengl_material const &mat = GfxRenderer->Material(m_material); + + // replace submodel selfillum with material one + if (!std::isnan(mat.selfillum)) + fLight = mat.selfillum; } } else { diff --git a/Model3d.h b/Model3d.h index ca479e19..43936838 100644 --- a/Model3d.h +++ b/Model3d.h @@ -12,8 +12,9 @@ http://mozilla.org/MPL/2.0/. #include "Classes.h" #include "dumb3d.h" #include "Float3d.h" -#include "openglgeometrybank.h" +#include "geometrybank.h" #include "material.h" +#include "gl/query.h" // Ra: specjalne typy submodeli, poza tym GL_TRIANGLES itp. const int TP_ROTATOR = 256; @@ -58,6 +59,7 @@ class TSubModel //m7todo: zrobić normalną serializację friend opengl_renderer; + friend opengl33_renderer; friend TModel3d; // temporary workaround. TODO: clean up class content/hierarchy friend TDynamicObject; // temporary etc friend scene::shape_node; // temporary etc @@ -151,7 +153,8 @@ public: // chwilowo private: int SeekFaceNormal( std::vector const &Masks, int const Startface, unsigned int const Mask, glm::vec3 const &Position, gfx::vertex_array const &Vertices ); void RaAnimation(TAnimType a); - // returns true if the submodel is a smoke emitter attachment point, false otherwise + void RaAnimation(glm::mat4 &m, TAnimType a); + // returns true if the submodel is a smoke emitter attachment point, false otherwise bool is_emitter() const; public: @@ -217,6 +220,8 @@ public: return m_material; } void ParentMatrix(float4x4 *m) const; float MaxY( float4x4 const &m ); + std::optional occlusion_query; + glm::mat4 future_transform; void deserialize(std::istream&); void serialize(std::ostream&, @@ -231,6 +236,7 @@ public: class TModel3d { friend opengl_renderer; + friend opengl33_renderer; private: TSubModel *Root; // drzewo submodeli diff --git a/PyInt.cpp b/PyInt.cpp index ea7720af..78eb4f61 100644 --- a/PyInt.cpp +++ b/PyInt.cpp @@ -40,7 +40,7 @@ void render_task::run() { ::glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); - if( GLEW_EXT_texture_filter_anisotropic ) { + if( GL_EXT_texture_filter_anisotropic ) { // anisotropic filtering ::glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, Global.AnisotropicFiltering ); } diff --git a/Texture.cpp b/Texture.cpp index 11872d34..55da13ab 100644 --- a/Texture.cpp +++ b/Texture.cpp @@ -17,7 +17,6 @@ http://mozilla.org/MPL/2.0/. #include "Texture.h" #include -#include "GL/glew.h" #include "application.h" #include "dictionary.h" @@ -29,6 +28,9 @@ http://mozilla.org/MPL/2.0/. #define EU07_DEFERRED_TEXTURE_UPLOAD +std::array opengl_texture::units = { 0 }; +GLint opengl_texture::m_activeunit = -1; + texture_manager::texture_manager() { // since index 0 is used to indicate no texture, we put a blank entry in the first texture slot @@ -545,16 +547,56 @@ opengl_texture::load_TGA() { } bool -opengl_texture::bind() { +opengl_texture::bind(size_t unit) { if( ( false == is_ready ) && ( false == create() ) ) { return false; } - ::glBindTexture( GL_TEXTURE_2D, id ); + + if (units[unit] == id) + return true; + + if (GLAD_GL_ARB_direct_state_access) + { + glBindTextureUnit(unit, id); + } + else + { + if (unit != m_activeunit) + { + glActiveTexture(GL_TEXTURE0 + unit); + m_activeunit = unit; + } + glBindTexture(target, id); + } + + units[unit] = id; + return true; } +void +opengl_texture::unbind(size_t unit) +{ + if (GLAD_GL_ARB_direct_state_access) + { + glBindTextureUnit(unit, 0); + } + else + { + if (unit != m_activeunit) + { + glActiveTexture(GL_TEXTURE0 + unit); + m_activeunit = unit; + } + //todo: for other targets + glBindTexture(GL_TEXTURE_2D, 0); + } + + units[unit] = 0; +} + bool opengl_texture::create() { @@ -697,6 +739,38 @@ opengl_texture::release() { return; } +void +opengl_texture::alloc_rendertarget( GLint format, GLint components, int width, int height, int s, GLint wrap ) { + + data_width = width; + data_height = height; + data_format = format; + data_components = components; + data_mapcount = 1; + is_rendertarget = true; + wrap_mode_s = wrap; + wrap_mode_t = wrap; + samples = s; + if( samples > 1 ) + target = GL_TEXTURE_2D_MULTISAMPLE; + create(); +} + +void +opengl_texture::set_components_hint( GLint hint ) { + + components_hint = hint; +} + +void +opengl_texture::reset_unit_cache() { + + for( auto &unit : units ) { + unit = 0; + } + m_activeunit = -1; +} + void opengl_texture::set_filtering() const { @@ -704,7 +778,7 @@ opengl_texture::set_filtering() const { ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); - if( GLEW_EXT_texture_filter_anisotropic ) { + if( GL_EXT_texture_filter_anisotropic ) { // anisotropic filtering ::glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, Global.AnisotropicFiltering ); } @@ -793,7 +867,7 @@ texture_manager::unit( GLint const Textureunit ) { // ustalenie numeru tekstury, wczytanie jeśli jeszcze takiej nie było texture_handle -texture_manager::create( std::string Filename, bool const Loadnow ) { +texture_manager::create( std::string Filename, bool const Loadnow, GLint Formathint ) { if( Filename.find( '|' ) != std::string::npos ) Filename.erase( Filename.find( '|' ) ); // po | może być nazwa kolejnej tekstury @@ -866,6 +940,7 @@ texture_manager::create( std::string Filename, bool const Loadnow ) { texture->name = locator.first; texture->type = locator.second; texture->traits = traits; + texture->components_hint = Formathint; auto const textureindex = (texture_handle)m_textures.size(); m_textures.emplace_back( texture, std::chrono::steady_clock::time_point() ); m_texturemappings.emplace( locator.first, textureindex ); @@ -888,40 +963,18 @@ texture_manager::create( std::string Filename, bool const Loadnow ) { void texture_manager::bind( std::size_t const Unit, texture_handle const Texture ) { - m_textures[ Texture ].second = m_garbagecollector.timestamp(); - if( m_units[ Unit ].unit == 0 ) { - // no texture unit, nothing to bind the texture to - return; - } - // even if we may skip texture binding make sure the relevant texture unit is activated - unit( m_units[ Unit ].unit ); - if( Texture == m_units[ Unit ].texture ) { - // don't bind again what's already active - return; - } - // TBD, TODO: do binding in texture object, add support for other types than 2d - if( Texture != null_handle ) { -#ifndef EU07_DEFERRED_TEXTURE_UPLOAD - // NOTE: we could bind dedicated 'error' texture here if the id isn't valid - ::glBindTexture( GL_TEXTURE_2D, texture(Texture).id ); - m_units[ Unit ].texture = Texture; -#else - if( true == texture( Texture ).bind() ) { - m_units[ Unit ].texture = Texture; - } - else { - // TODO: bind a special 'error' texture on failure - ::glBindTexture( GL_TEXTURE_2D, 0 ); - m_units[ Unit ].texture = 0; - } -#endif - } - else { - ::glBindTexture( GL_TEXTURE_2D, 0 ); - m_units[ Unit ].texture = 0; - } - // all done - return; + if (Texture != null_handle) + mark_as_used(Texture).bind(Unit); + else + opengl_texture::unbind(Unit); +} + +opengl_texture & +texture_manager::mark_as_used(const texture_handle Texture) { + + auto &pair = m_textures[ Texture ]; + pair.second = m_garbagecollector.timestamp(); + return *pair.first; } void diff --git a/Texture.h b/Texture.h index 626bf07c..7764c523 100644 --- a/Texture.h +++ b/Texture.h @@ -12,8 +12,8 @@ http://mozilla.org/MPL/2.0/. #include #include #include -#include "GL/glew.h" #include "ResourceManager.h" +#include "gl/ubo.h" struct opengl_texture { static DDSURFACEDESC2 deserialize_ddsd(std::istream&); @@ -27,12 +27,20 @@ struct opengl_texture { void load(); bool - bind(); + bind( size_t unit ); + static void + unbind( size_t unit ); bool create(); // releases resources allocated on the opengl end, storing local copy if requested void release(); + void + alloc_rendertarget( GLint format, GLint components, int width, int height, int samples = 1, GLint wrap = GL_CLAMP_TO_BORDER ); + void + set_components_hint( GLint hint ); + static void + reset_unit_cache(); inline int width() const { @@ -49,6 +57,10 @@ struct opengl_texture { std::string name; // name of the texture source file std::string type; // type of the texture source file std::size_t size{ 0 }; // size of the texture data, in kb + GLint components_hint = 0; // components that material wants + + GLenum target = GL_TEXTURE_2D; + static std::array units; private: // methods @@ -63,6 +75,8 @@ private: void flip_vertical(); // 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) resource_state data_state{ resource_state::none }; // current state of texture data int data_width{ 0 }, @@ -70,10 +84,18 @@ private: data_mapcount{ 0 }; GLint data_format{ 0 }, data_components{ 0 }; + GLint data_type = GL_UNSIGNED_BYTE; + GLint wrap_mode_s = GL_REPEAT; + GLint wrap_mode_t = GL_REPEAT; /* std::atomic is_loaded{ false }; // indicates the texture data was loaded and can be processed std::atomic is_good{ false }; // indicates the texture data was retrieved without errors */ + static std::unordered_map precompressed_formats; + static std::unordered_map drivercompressed_formats; + static std::unordered_map> mapping; + + static GLint m_activeunit; }; typedef int texture_handle; @@ -91,10 +113,12 @@ public: unit( GLint const Textureunit ); // creates texture object out of data stored in specified file texture_handle - create( std::string Filename, bool const Loadnow = true ); + create( std::string Filename, bool const Loadnow = true, GLint Formathint = GL_SRGB_ALPHA ); // binds specified texture to specified texture unit void bind( std::size_t const Unit, texture_handle const Texture ); + opengl_texture & + mark_as_used( texture_handle const Texture ); // provides direct access to specified texture object opengl_texture & texture( texture_handle const Texture ) const { return *(m_textures[ Texture ].first); } diff --git a/Timer.h b/Timer.h index ffc15cb7..2aa69728 100644 --- a/Timer.h +++ b/Timer.h @@ -54,10 +54,12 @@ struct subsystem_stopwatches { stopwatch gfx_shadows; stopwatch gfx_reflections; stopwatch gfx_swap; + stopwatch gfx_gui; stopwatch sim_total; stopwatch sim_dynamics; stopwatch sim_events; stopwatch sim_ai; + stopwatch mainloop_total; }; extern subsystem_stopwatches subsystem; diff --git a/Track.cpp b/Track.cpp index 0a029eba..57f2decb 100644 --- a/Track.cpp +++ b/Track.cpp @@ -585,11 +585,11 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) else if (iCategoryFlag & 2) if (m_material1 && fTexLength) { // dla drogi trzeba ustalić proporcje boków nawierzchni - auto const &texture1 { GfxRenderer->Texture( GfxRenderer->Material( m_material1 ).texture1 ) }; + auto const &texture1 { GfxRenderer->Texture( GfxRenderer->Material( m_material1 ).textures[0] ) }; if( texture1.height() > 0 ) { fTexRatio1 = static_cast( texture1.width() ) / static_cast( texture1.height() ); // proporcja boków } - auto const &texture2 { GfxRenderer->Texture( GfxRenderer->Material( m_material2 ).texture1 ) }; + auto const &texture2 { GfxRenderer->Texture( GfxRenderer->Material( m_material2 ).textures[0] ) }; if( texture2.height() > 0 ) { fTexRatio2 = static_cast( texture2.width() ) / static_cast( texture2.height() ); // proporcja boków } diff --git a/Track.h b/Track.h index 2e564c11..67525d7a 100644 --- a/Track.h +++ b/Track.h @@ -144,6 +144,7 @@ private: class TTrack : public scene::basic_node { friend opengl_renderer; + friend opengl33_renderer; // NOTE: temporary arrangement friend itemproperties_panel; diff --git a/Traction.h b/Traction.h index d9e2778f..bb081bea 100644 --- a/Traction.h +++ b/Traction.h @@ -21,6 +21,7 @@ class TTractionPowerSource; class TTraction : public scene::basic_node { friend opengl_renderer; + friend opengl33_renderer; public: // na razie TTractionPowerSource *psPower[ 2 ] { nullptr, nullptr }; // najbliższe zasilacze z obu kierunków diff --git a/Train.cpp b/Train.cpp index 708b598d..b807d13f 100644 --- a/Train.cpp +++ b/Train.cpp @@ -7558,7 +7558,7 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) ( substr_path(renderername).empty() ? // supply vehicle folder as path if none is provided DynamicObject->asBaseDir + renderername : renderername ), - GfxRenderer->Material( material ).texture1 ); + GfxRenderer->Material( material ).textures[0] ); } // btLampkaUnknown.Init("unknown",mdKabina,false); } while (token != ""); diff --git a/application.cpp b/application.cpp index 57f10bd9..4edfabd8 100644 --- a/application.cpp +++ b/application.cpp @@ -25,14 +25,12 @@ http://mozilla.org/MPL/2.0/. #ifdef EU07_BUILD_STATIC #pragma comment( lib, "glfw3.lib" ) -#pragma comment( lib, "glew32s.lib" ) #else #ifdef _WIN32 #pragma comment( lib, "glfw3dll.lib" ) #else #pragma comment( lib, "glfw3.lib" ) #endif -#pragma comment( lib, "glew32.lib" ) #endif // build_static #pragma comment( lib, "opengl32.lib" ) #pragma comment( lib, "glu32.lib" ) @@ -125,10 +123,10 @@ eu07_application::init( int Argc, char *Argv[] ) { if( ( result = init_glfw() ) != 0 ) { return result; } - init_callbacks(); if( ( result = init_gfx() ) != 0 ) { return result; } + init_callbacks(); if( ( result = init_audio() ) != 0 ) { return result; } @@ -255,6 +253,16 @@ eu07_application::set_cursor_pos( double const Horizontal, double const Vertical glfwSetCursorPos( m_windows.front(), Horizontal, Vertical ); } +glm::dvec2 +eu07_application::get_cursor_pos() const { + + glm::dvec2 pos; + if( !m_windows.empty() ) { + glfwGetCursorPos( m_windows.front(), &pos.x, &pos.y ); + } + return pos; +} + void eu07_application::get_cursor_pos( double &Horizontal, double &Vertical ) const { @@ -320,10 +328,12 @@ eu07_application::init_debug() { // memory leaks _CrtSetDbgFlag( _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG ) | _CRTDBG_LEAK_CHECK_DF ); // floating point operation errors + /* auto state { _clearfp() }; state = _control87( 0, 0 ); // this will turn on FPE for #IND and zerodiv state = _control87( state & ~( _EM_ZERODIVIDE | _EM_INVALID ), _MCW_EM ); + */ #endif #ifdef _WIN32 ::SetUnhandledExceptionFilter( unhandled_handler ); @@ -507,18 +517,38 @@ eu07_application::init_callbacks() { int eu07_application::init_gfx() { - if( glewInit() != GLEW_OK ) { - ErrorLog( "Bad init: failed to initialize glew" ); - return -1; + if (Global.gfx_usegles) + { + if( 0 == gladLoadGLES2Loader( (GLADloadproc)glfwGetProcAddress ) ) { + ErrorLog( "Bad init: failed to initialize glad" ); + return -1; + } + } + else + { + if( 0 == gladLoadGLLoader( (GLADloadproc)glfwGetProcAddress ) ) { + ErrorLog( "Bad init: failed to initialize glad" ); + return -1; + } } - GfxRenderer = std::make_unique(); - - if( ( false == GfxRenderer->Init( m_windows.front() ) ) - || ( false == ui_layer::init( m_windows.front() ) ) ) { - return -1; + { + // legacy render path + GfxRenderer = std::make_unique(); + Global.DisabledLogTypes |= logtype::material; } + if( false == GfxRenderer->Init( m_windows.front() ) ) { + return -1; + } + if( false == ui_layer::init( m_windows.front() ) ) { + return -1; + } +/* + for (const global_settings::extraviewport_config &conf : Global.extra_viewports) + if (!GfxRenderer.AddViewport(conf)) + return -1; +*/ return 0; } diff --git a/application.h b/application.h index 86b9993f..815c0e91 100644 --- a/application.h +++ b/application.h @@ -56,6 +56,8 @@ public: set_cursor( int const Mode ); void set_cursor_pos( double const Horizontal, double const Vertical ); + glm::dvec2 + get_cursor_pos() const; void get_cursor_pos( double &Horizontal, double &Vertical ) const; // input handlers diff --git a/drivermouseinput.cpp b/drivermouseinput.cpp index d3e42c76..a8dae764 100644 --- a/drivermouseinput.cpp +++ b/drivermouseinput.cpp @@ -245,9 +245,7 @@ drivermouse_input::move( double Mousex, double Mousey ) { Mousex, Mousey, GLFW_PRESS, - // as we haven't yet implemented either item id system or multiplayer, the 'local' controlled vehicle and entity have temporary ids of 0 - // TODO: pass correct entity id once the missing systems are in place - 0 ); + 0 ); } else { // control picking mode @@ -258,8 +256,7 @@ drivermouse_input::move( double Mousex, double Mousey ) { m_slider.value(), 0, GLFW_PRESS, - // TODO: pass correct entity id once the missing systems are in place - 0 ); + 0 ); } if( false == m_pickmodepanning ) { @@ -275,9 +272,7 @@ drivermouse_input::move( double Mousex, double Mousey ) { viewoffset.x, viewoffset.y, GLFW_PRESS, - // as we haven't yet implemented either item id system or multiplayer, the 'local' controlled vehicle and entity have temporary ids of 0 - // TODO: pass correct entity id once the missing systems are in place - 0 ); + 0 ); m_cursorposition = cursorposition; } } @@ -287,7 +282,7 @@ drivermouse_input::scroll( double const Xoffset, double const Yoffset ) { if( Global.ctrlState ) { // ctrl + scroll wheel adjusts fov - Global.FieldOfView = clamp( static_cast( Global.FieldOfView - Yoffset * 20.0 / Timer::subsystem.gfx_total.average() ), 15.0f, 75.0f ); + Global.FieldOfView = clamp( static_cast( Global.FieldOfView - Yoffset * 20.0 / Timer::subsystem.mainloop_total.average() ), 15.0f, 75.0f ); } else { // scroll adjusts master controller @@ -323,12 +318,12 @@ 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 ) { - auto const *node { GfxRenderer->Update_Pick_Node() }; - if( ( node == nullptr ) - || ( typeid( *node ) != typeid( TAnimModel ) ) ) { - return; - } - simulation::Region->on_click( static_cast( node ) ); + GfxRenderer->Pick_Node( + [this](scene::basic_node *node) { + if( ( node == nullptr ) + || ( typeid( *node ) != typeid( TAnimModel ) ) ) + return; + simulation::Region->on_click( static_cast( node ) ); } ); } } // right button controls panning @@ -349,10 +344,11 @@ drivermouse_input::button( int const Button, int const Action ) { // NOTE: basic keyboard controls don't have any parameters // as we haven't yet implemented either item id system or multiplayer, the 'local' controlled vehicle and entity have temporary ids of 0 // TODO: pass correct entity id once the missing systems are in place - m_relay.post( mousecommand, 0, 0, Action, 0 ); + m_relay.post( mousecommand, 0, 0, Action, 0 ); mousecommand = user_command::none; } else { + m_pickwaiting = false; if( Button == GLFW_MOUSE_BUTTON_LEFT ) { if( m_slider.command() != user_command::none ) { m_relay.post( m_slider.command(), 0, 0, Action, 0 ); @@ -369,68 +365,77 @@ drivermouse_input::button( int const Button, int const Action ) { } else { // if not release then it's press - auto const lookup = m_buttonbindings.find( simulation::Train->GetLabel( GfxRenderer->Update_Pick_Control() ) ); - if( lookup != m_buttonbindings.end() ) { - // if the recognized element under the cursor has a command associated with the pressed button, notify the recipient - mousecommand = ( - Button == GLFW_MOUSE_BUTTON_LEFT ? - lookup->second.left : - lookup->second.right - ); - if( mousecommand == user_command::none ) { return; } - // check manually for commands which have 'fast' variants launched with shift modifier - if( Global.shiftState ) { - switch( mousecommand ) { - case user_command::mastercontrollerincrease: { mousecommand = user_command::mastercontrollerincreasefast; break; } - case user_command::mastercontrollerdecrease: { mousecommand = user_command::mastercontrollerdecreasefast; break; } - case user_command::secondcontrollerincrease: { mousecommand = user_command::secondcontrollerincreasefast; break; } - case user_command::secondcontrollerdecrease: { mousecommand = user_command::secondcontrollerdecreasefast; break; } - case user_command::independentbrakeincrease: { mousecommand = user_command::independentbrakeincreasefast; break; } - case user_command::independentbrakedecrease: { mousecommand = user_command::independentbrakedecreasefast; break; } - default: { break; } - } - } + m_pickwaiting = true; + GfxRenderer->Pick_Control( + [this, Button, Action, &mousecommand](TSubModel const *control) { - switch( mousecommand ) { - case user_command::mastercontrollerincrease: - case user_command::mastercontrollerdecrease: - case user_command::secondcontrollerincrease: - case user_command::secondcontrollerdecrease: - case user_command::trainbrakeincrease: - case user_command::trainbrakedecrease: - case user_command::independentbrakeincrease: - case user_command::independentbrakedecrease: { - // these commands trigger varying repeat rate mode, - // which scales the rate based on the distance of the cursor from its point when the command was first issued - m_varyingpollrateorigin = m_cursorposition; - m_varyingpollrate = true; - break; - } - case user_command::jointcontrollerset: - case user_command::mastercontrollerset: - case user_command::secondcontrollerset: - case user_command::trainbrakeset: - case user_command::independentbrakeset: { - m_slider.bind( mousecommand ); - mousecommand = user_command::none; - return; - } - default: { - break; - } - } - // NOTE: basic keyboard controls don't have any parameters - // NOTE: as we haven't yet implemented either item id system or multiplayer, the 'local' controlled vehicle and entity have temporary ids of 0 - // TODO: pass correct entity id once the missing systems are in place - m_relay.post( mousecommand, 0, 0, Action, 0 ); - m_updateaccumulator = -0.25; // prevent potential command repeat right after issuing one - } - else { - // if we don't have any recognized element under the cursor and the right button was pressed, enter view panning mode - if( Button == GLFW_MOUSE_BUTTON_RIGHT ) { - m_pickmodepanning = true; - } - } + bool pickwaiting = m_pickwaiting; + m_pickwaiting = false; + + auto const lookup = m_buttonbindings.find( simulation::Train->GetLabel( control ) ); + if( lookup != m_buttonbindings.end() ) { + // if the recognized element under the cursor has a command associated with the pressed button, notify the recipient + mousecommand = ( + Button == GLFW_MOUSE_BUTTON_LEFT ? + lookup->second.left : + lookup->second.right + ); + if( mousecommand == user_command::none ) { return; } + // check manually for commands which have 'fast' variants launched with shift modifier + if( Global.shiftState ) { + switch( mousecommand ) { + case user_command::mastercontrollerincrease: { mousecommand = user_command::mastercontrollerincreasefast; break; } + case user_command::mastercontrollerdecrease: { mousecommand = user_command::mastercontrollerdecreasefast; break; } + case user_command::secondcontrollerincrease: { mousecommand = user_command::secondcontrollerincreasefast; break; } + case user_command::secondcontrollerdecrease: { mousecommand = user_command::secondcontrollerdecreasefast; break; } + case user_command::independentbrakeincrease: { mousecommand = user_command::independentbrakeincreasefast; break; } + case user_command::independentbrakedecrease: { mousecommand = user_command::independentbrakedecreasefast; break; } + default: { break; } + } + } + + switch( mousecommand ) { + case user_command::mastercontrollerincrease: + case user_command::mastercontrollerdecrease: + case user_command::secondcontrollerincrease: + case user_command::secondcontrollerdecrease: + case user_command::trainbrakeincrease: + case user_command::trainbrakedecrease: + case user_command::independentbrakeincrease: + case user_command::independentbrakedecrease: { + // these commands trigger varying repeat rate mode, + // which scales the rate based on the distance of the cursor from its point when the command was first issued + m_varyingpollrateorigin = m_cursorposition; + m_varyingpollrate = true; + break; + } + case user_command::jointcontrollerset: + case user_command::mastercontrollerset: + case user_command::secondcontrollerset: + case user_command::trainbrakeset: + case user_command::independentbrakeset: { + m_slider.bind( mousecommand ); + mousecommand = user_command::none; + return; + } + default: { + break; + } + } + + // NOTE: basic keyboard controls don't have any parameters + // NOTE: as we haven't yet implemented either item id system or multiplayer, the 'local' controlled vehicle and entity have temporary ids of 0 + // TODO: pass correct entity id once the missing systems are in place + m_relay.post( mousecommand, 0, 0, Action, 0 ); + if (!pickwaiting) // already depressed + m_relay.post( mousecommand, 0, 0, GLFW_RELEASE, 0 ); + m_updateaccumulator = -0.25; // prevent potential command repeat right after issuing one + } + else { + // if we don't have any recognized element under the cursor and the right button was pressed, enter view panning mode + if( Button == GLFW_MOUSE_BUTTON_RIGHT ) { + m_pickmodepanning = true; + } } } ); } } } @@ -457,13 +462,13 @@ drivermouse_input::poll() { // NOTE: basic keyboard controls don't have any parameters // as we haven't yet implemented either item id system or multiplayer, the 'local' controlled vehicle and entity have temporary ids of 0 // TODO: pass correct entity id once the missing systems are in place - m_relay.post( m_mousecommandleft, 0, 0, GLFW_REPEAT, 0 ); + m_relay.post( m_mousecommandleft, 0, 0, GLFW_REPEAT, 0 ); } if( m_mousecommandright != user_command::none ) { // NOTE: basic keyboard controls don't have any parameters // as we haven't yet implemented either item id system or multiplayer, the 'local' controlled vehicle and entity have temporary ids of 0 // TODO: pass correct entity id once the missing systems are in place - m_relay.post( m_mousecommandright, 0, 0, GLFW_REPEAT, 0 ); + m_relay.post( m_mousecommandright, 0, 0, GLFW_REPEAT, 0 ); } m_updateaccumulator -= updaterate; } diff --git a/drivermouseinput.h b/drivermouseinput.h index 2932a722..af1928ff 100644 --- a/drivermouseinput.h +++ b/drivermouseinput.h @@ -105,6 +105,7 @@ private: bool m_varyingpollrate { false }; // indicates rate of command repeats is affected by the cursor position glm::dvec2 m_varyingpollrateorigin; // helper, cursor position when the command was initiated std::array m_buttons; + bool m_pickwaiting; }; -//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- \ No newline at end of file diff --git a/editormode.cpp b/editormode.cpp index 42a52236..a60d278f 100644 --- a/editormode.cpp +++ b/editormode.cpp @@ -229,11 +229,14 @@ editor_mode::on_mouse_button( int const Button, int const Action, int const Mods if( Action == GLFW_PRESS ) { // left button press - m_node = GfxRenderer->Update_Pick_Node(); - if( m_node ) { - Application.set_cursor( GLFW_CURSOR_DISABLED ); - } - dynamic_cast( m_userinterface.get() )->set_node( m_node ); + m_node = nullptr; + GfxRenderer->Pick_Node( + [ this ]( scene::basic_node *node ) { + m_node = node; + if( m_node ) { + Application.set_cursor( GLFW_CURSOR_DISABLED ); + } + dynamic_cast( m_userinterface.get() )->set_node( m_node ); } ); } else { // left button release diff --git a/editoruipanels.h b/editoruipanels.h index efd6f8cd..4ed9a83a 100644 --- a/editoruipanels.h +++ b/editoruipanels.h @@ -11,7 +11,21 @@ http://mozilla.org/MPL/2.0/. #include "uilayer.h" #include "Classes.h" +/* +// helper, associated bool is set when the primary value was changed and expects processing at the observer's leisure +template +using changeable = std::pair; +// helper, holds a set of changeable properties for a scene node +struct item_properties { + + scene::basic_node const *node { nullptr }; // properties' owner + + changeable name {}; + changeable location {}; + changeable rotation {}; +}; +*/ class itemproperties_panel : public ui_panel { public: diff --git a/geometrybank.cpp b/geometrybank.cpp index 58bfab50..31ef6459 100644 --- a/geometrybank.cpp +++ b/geometrybank.cpp @@ -47,6 +47,113 @@ basic_vertex::deserialize( std::istream &s ) { texture.y = sn_utils::ld_float32( s ); } +// based on +// Lengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”. +// Terathon Software, 2001. http://terathon.com/code/tangent.html +void calculate_tangent(vertex_array &vertices, int type) +{ + size_t vertex_count = vertices.size(); + + if (!vertex_count || vertices[0].tangent.w != 0.0f) + return; + + size_t triangle_count; + if (type == GL_TRIANGLES) + triangle_count = vertex_count / 3; + else if (type == GL_TRIANGLE_STRIP) + triangle_count = vertex_count - 2; + else if (type == GL_TRIANGLE_FAN) + triangle_count = vertex_count - 2; + else + return; + + std::vector tan(vertex_count * 2); + + for (size_t a = 0; a < triangle_count; a++) + { + size_t i1, i2, i3; + if (type == GL_TRIANGLES) + { + i1 = a * 3; + i2 = a * 3 + 1; + i3 = a * 3 + 2; + } + else if (type == GL_TRIANGLE_STRIP) + { + if (a % 2 == 0) + { + i1 = a; + i2 = a + 1; + } + else + { + i1 = a + 1; + i2 = a; + } + i3 = a + 2; + } + else if (type == GL_TRIANGLE_FAN) + { + i1 = 0; + i2 = a + 1; + i3 = a + 2; + } + + const glm::vec3 &v1 = vertices[i1].position; + const glm::vec3 &v2 = vertices[i2].position; + const glm::vec3 &v3 = vertices[i3].position; + + const glm::vec2 &w1 = vertices[i1].texture; + const glm::vec2 &w2 = vertices[i2].texture; + const glm::vec2 &w3 = vertices[i3].texture; + + float x1 = v2.x - v1.x; + float x2 = v3.x - v1.x; + float y1 = v2.y - v1.y; + float y2 = v3.y - v1.y; + float z1 = v2.z - v1.z; + float z2 = v3.z - v1.z; + + float s1 = w2.x - w1.x; + float s2 = w3.x - w1.x; + float t1 = w2.y - w1.y; + float t2 = w3.y - w1.y; + + float ri = (s1 * t2 - s2 * t1); + if (ri == 0.0f) { + //ErrorLog("Bad model: failed to generate tangent vectors for vertices: " + + // std::to_string(i1) + ", " + std::to_string(i2) + ", " + std::to_string(i3)); + // useless error, as we don't have name of problematic model here + // why does it happen? + ri = 1.0f; + } + + float r = 1.0f / ri; + glm::vec3 sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, + (t2 * z1 - t1 * z2) * r); + glm::vec3 tdir((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, + (s1 * z2 - s2 * z1) * r); + + tan[i1] += sdir; + tan[i2] += sdir; + tan[i3] += sdir; + + tan[vertex_count + i1] += tdir; + tan[vertex_count + i2] += tdir; + tan[vertex_count + i3] += tdir; + } + + for (size_t a = 0; a < vertex_count; a++) + { + const glm::vec3 &n = vertices[a].normal; + const glm::vec3 &t = tan[a]; + const glm::vec3 &t2 = tan[vertex_count + a]; + + vertices[a].tangent = glm::vec4(glm::normalize((t - n * glm::dot(n, t))), + (glm::dot(glm::cross(n, t), t2) < 0.0F) ? -1.0F : 1.0F); + } +} + // generic geometry bank class, allows storage, update and drawing of geometry chunks // creates a new geometry chunk of specified type from supplied vertex data. returns: handle to the chunk diff --git a/geometrybank.h b/geometrybank.h index 6c9be0b3..d3c14007 100644 --- a/geometrybank.h +++ b/geometrybank.h @@ -9,13 +9,6 @@ http://mozilla.org/MPL/2.0/. #pragma once -#include -#include -#include -#include "GL/glew.h" -#ifdef _WIN32 -#include "GL/wglew.h" -#endif #include "ResourceManager.h" namespace gfx { @@ -25,6 +18,7 @@ struct basic_vertex { glm::vec3 position; // 3d space glm::vec3 normal; // 3d space glm::vec2 texture; // uv space + glm::vec4 tangent; // xyz - tangent, w - handedness basic_vertex() = default; basic_vertex( glm::vec3 Position, glm::vec3 Normal, glm::vec2 Texture ) : @@ -53,6 +47,8 @@ struct stream_units { using vertex_array = std::vector; +void calculate_tangent( vertex_array &vertices, int type ); + // generic geometry bank class, allows storage, update and drawing of geometry chunks struct geometry_handle { diff --git a/gl/bindable.h b/gl/bindable.h new file mode 100644 index 00000000..b845dadb --- /dev/null +++ b/gl/bindable.h @@ -0,0 +1,27 @@ +#pragma once + +namespace gl +{ + template + class bindable + { + protected: + thread_local static bindable* active; + + public: + void bind() + { + if (active == this) + return; + active = this; + T::bind(*static_cast(active)); + } + static void unbind() + { + active = nullptr; + T::bind(0); + } + }; + + template thread_local bindable* bindable::active; +} diff --git a/gl/buffer.cpp b/gl/buffer.cpp new file mode 100644 index 00000000..2aa4b5e3 --- /dev/null +++ b/gl/buffer.cpp @@ -0,0 +1,89 @@ +#include "stdafx.h" +#include "buffer.h" + +GLenum gl::buffer::glenum_target(gl::buffer::targets target) +{ + static GLenum mapping[13] = + { + GL_ARRAY_BUFFER, + GL_ATOMIC_COUNTER_BUFFER, + GL_COPY_READ_BUFFER, + GL_COPY_WRITE_BUFFER, + GL_DISPATCH_INDIRECT_BUFFER, + GL_DRAW_INDIRECT_BUFFER, + GL_ELEMENT_ARRAY_BUFFER, + GL_PIXEL_PACK_BUFFER, + GL_PIXEL_UNPACK_BUFFER, + GL_SHADER_STORAGE_BUFFER, + GL_TEXTURE_BUFFER, + GL_TRANSFORM_FEEDBACK_BUFFER, + GL_UNIFORM_BUFFER + }; + return mapping[target]; +} + +void gl::buffer::bind(targets target) +{ + if (binding_points[target] == *this) + return; + + glBindBuffer(glenum_target(target), *this); + binding_points[target] = *this; +} + +void gl::buffer::bind_base(targets target, GLuint index) +{ + glBindBufferBase(glenum_target(target), index, *this); + binding_points[target] = *this; +} + +void gl::buffer::unbind(targets target) +{ + glBindBuffer(glenum_target(target), 0); + binding_points[target] = 0; +} + +void gl::buffer::unbind() +{ + for (size_t i = 0; i < sizeof(binding_points) / sizeof(GLuint); i++) + unbind((targets)i); +} + +gl::buffer::buffer() +{ + glGenBuffers(1, *this); +} + +gl::buffer::~buffer() +{ + glDeleteBuffers(1, *this); +} + +void gl::buffer::allocate(targets target, int size, GLenum hint) +{ + bind(target); + glBufferData(glenum_target(target), size, nullptr, hint); +} + +void gl::buffer::upload(targets target, const void *data, int offset, int size) +{ + bind(target); + glBufferSubData(glenum_target(target), offset, size, data); +} + +void gl::buffer::download(targets target, void *data, int offset, int size) +{ + bind(target); + if (GLAD_GL_VERSION_3_3) + { + glGetBufferSubData(glenum_target(target), offset, size, data); + } + else + { + void *glbuf = glMapBufferRange(glenum_target(target), offset, size, GL_MAP_READ_BIT); + memcpy(data, glbuf, size); + glUnmapBuffer(glenum_target(target)); + } +} + +thread_local GLuint gl::buffer::binding_points[13]; diff --git a/gl/buffer.h b/gl/buffer.h new file mode 100644 index 00000000..c8ff232e --- /dev/null +++ b/gl/buffer.h @@ -0,0 +1,46 @@ +#pragma once + +#include "object.h" +#include "bindable.h" + +namespace gl +{ + class buffer : public object + { + thread_local static GLuint binding_points[13]; + + public: + enum targets + { + ARRAY_BUFFER = 0, + ATOMIC_COUNTER_BUFFER, + COPY_READ_BUFFER, + COPY_WRITE_BUFFER, + DISPATCH_INDIRECT_BUFFER, + DRAW_INDIRECT_BUFFER, + ELEMENT_ARRAY_BUFFER, + PIXEL_PACK_BUFFER, + PIXEL_UNPACK_BUFFER, + SHADER_STORAGE_BUFFER, + TEXTURE_BUFFER, + TRANSFORM_FEEDBACK_BUFFER, + UNIFORM_BUFFER + }; + + protected: + static GLenum glenum_target(targets target); + + public: + buffer(); + ~buffer(); + + void bind(targets target); + void bind_base(targets target, GLuint index); + static void unbind(targets target); + static void unbind(); + + void allocate(targets target, int size, GLenum hint); + void upload(targets target, const void *data, int offset, int size); + void download(targets target, void *data, int offset, int size); + }; +} diff --git a/gl/cubemap.cpp b/gl/cubemap.cpp new file mode 100644 index 00000000..89ef9d1b --- /dev/null +++ b/gl/cubemap.cpp @@ -0,0 +1,38 @@ +#include "stdafx.h" +#include "cubemap.h" + +gl::cubemap::cubemap() +{ + glGenTextures(1, *this); + + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); +} + +gl::cubemap::~cubemap() +{ + glDeleteTextures(1, *this); +} + +void gl::cubemap::alloc(GLint format, int width, int height, GLenum components, GLenum type) +{ + glBindTexture(GL_TEXTURE_CUBE_MAP, *this); + for (GLuint tgt = GL_TEXTURE_CUBE_MAP_POSITIVE_X; tgt <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; tgt++) + glTexImage2D(tgt, 0, format, width, height, 0, components, type, nullptr); + + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +} + +void gl::cubemap::bind(int unit) +{ + glActiveTexture(unit); + glBindTexture(GL_TEXTURE_CUBE_MAP, *this); +} + +void gl::cubemap::generate_mipmaps() +{ + glBindTexture(GL_TEXTURE_CUBE_MAP, *this); + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +} diff --git a/gl/cubemap.h b/gl/cubemap.h new file mode 100644 index 00000000..e874c221 --- /dev/null +++ b/gl/cubemap.h @@ -0,0 +1,19 @@ +#pragma once + +#include "object.h" + +namespace gl +{ + // cubemap texture rendertarget + // todo: integrate with texture system + class cubemap : public object + { + public: + cubemap(); + ~cubemap(); + + void alloc(GLint format, int width, int height, GLenum components, GLenum type); + void bind(int unit); + void generate_mipmaps(); + }; +} diff --git a/gl/fence.cpp b/gl/fence.cpp new file mode 100644 index 00000000..92f31663 --- /dev/null +++ b/gl/fence.cpp @@ -0,0 +1,20 @@ +#include "stdafx.h" +#include "fence.h" + +gl::fence::fence() +{ + sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); +} + +gl::fence::~fence() +{ + glDeleteSync(sync); +} + +bool gl::fence::is_signalled() +{ + GLsizei len = 0; + GLint val; + glGetSynciv(sync, GL_SYNC_STATUS, 1, &len, &val); + return len == 1 && val == GL_SIGNALED; +} diff --git a/gl/fence.h b/gl/fence.h new file mode 100644 index 00000000..50564163 --- /dev/null +++ b/gl/fence.h @@ -0,0 +1,20 @@ +#pragma once + +#include "object.h" + +namespace gl +{ + class fence + { + GLsync sync; + + public: + fence(); + ~fence(); + + bool is_signalled(); + + fence(const fence&) = delete; + fence& operator=(const fence&) = delete; + }; +} diff --git a/gl/framebuffer.cpp b/gl/framebuffer.cpp new file mode 100644 index 00000000..6c347ab9 --- /dev/null +++ b/gl/framebuffer.cpp @@ -0,0 +1,103 @@ +#include "stdafx.h" +#include "framebuffer.h" + +gl::framebuffer::framebuffer() +{ + glGenFramebuffers(1, *this); +} + +gl::framebuffer::~framebuffer() +{ + glDeleteFramebuffers(1, *this); +} + +void gl::framebuffer::bind(GLuint id) +{ + glBindFramebuffer(GL_FRAMEBUFFER, id); +} + +void gl::framebuffer::attach(const opengl_texture &tex, GLenum location) +{ + bind(); + glFramebufferTexture2D(GL_FRAMEBUFFER, location, tex.target, tex.id, 0); +} + +void gl::framebuffer::attach(const cubemap &tex, int face, GLenum location) +{ + bind(); + glFramebufferTexture2D(GL_FRAMEBUFFER, location, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, *tex, 0); +} + +void gl::framebuffer::attach(const renderbuffer &rb, GLenum location) +{ + bind(); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, location, GL_RENDERBUFFER, *rb); +} + +void gl::framebuffer::detach(GLenum location) +{ + bind(); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, location, GL_RENDERBUFFER, 0); +} + +bool gl::framebuffer::is_complete() +{ + bind(); + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + return status == GL_FRAMEBUFFER_COMPLETE; +} + +void gl::framebuffer::clear(GLbitfield mask) +{ + bind(); + if (mask & GL_DEPTH_BUFFER_BIT) + glDepthMask(GL_TRUE); + glClear(mask); +} + +void gl::framebuffer::blit_to(framebuffer *other, int w, int h, GLbitfield mask, GLenum attachment) +{ + blit(this, other, 0, 0, w, h, mask, attachment); +} + +void gl::framebuffer::blit_from(framebuffer *other, int w, int h, GLbitfield mask, GLenum attachment) +{ + blit(other, this, 0, 0, w, h, mask, attachment); +} + +void gl::framebuffer::blit(framebuffer *src, framebuffer *dst, int sx, int sy, int w, int h, GLbitfield mask, GLenum attachment) +{ + glBindFramebuffer(GL_READ_FRAMEBUFFER, src ? *src : 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst ? *dst : 0); + + if (mask & GL_COLOR_BUFFER_BIT) + { + int attachment_n = attachment - GL_COLOR_ATTACHMENT0; + + GLenum outputs[8] = { GL_NONE }; + outputs[attachment_n] = attachment; + + glReadBuffer(attachment); + glDrawBuffers(attachment_n + 1, outputs); + } + + glBlitFramebuffer(sx, sy, sx + w, sy + h, 0, 0, w, h, mask, GL_NEAREST); + unbind(); +} + +void gl::framebuffer::setup_drawing(int attachments) +{ + bind(); + GLenum a[8] = + { + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1, + GL_COLOR_ATTACHMENT2, + GL_COLOR_ATTACHMENT3, + GL_COLOR_ATTACHMENT4, + GL_COLOR_ATTACHMENT5, + GL_COLOR_ATTACHMENT6, + GL_COLOR_ATTACHMENT7 + }; + glDrawBuffers(std::min(attachments, 8), &a[0]); +} diff --git a/gl/framebuffer.h b/gl/framebuffer.h new file mode 100644 index 00000000..37bdefc2 --- /dev/null +++ b/gl/framebuffer.h @@ -0,0 +1,33 @@ +#pragma once + +#include "object.h" +#include "bindable.h" +#include "renderbuffer.h" +#include "Texture.h" +#include "cubemap.h" + +namespace gl +{ + class framebuffer : public object, public bindable + { + public: + framebuffer(); + ~framebuffer(); + + void attach(const opengl_texture &tex, GLenum location); + void attach(const cubemap &tex, int face, GLenum location); + void attach(const renderbuffer &rb, GLenum location); + void setup_drawing(int attachments); + void detach(GLenum location); + void clear(GLbitfield mask); + + bool is_complete(); + void blit_to(framebuffer *other, int w, int h, GLbitfield mask, GLenum attachment); + void blit_from(framebuffer *other, int w, int h, GLbitfield mask, GLenum attachment); + + static void blit(framebuffer *src, framebuffer *dst, int sx, int sy, int w, int h, GLbitfield mask, GLenum attachment); + + using bindable::bind; + static void bind(GLuint id); + }; +} diff --git a/gl/glsl_common.cpp b/gl/glsl_common.cpp new file mode 100644 index 00000000..4215a475 --- /dev/null +++ b/gl/glsl_common.cpp @@ -0,0 +1,72 @@ +#include "stdafx.h" +#include "glsl_common.h" + +std::string gl::glsl_common; + +void gl::glsl_common_setup() +{ + glsl_common = + "#define SHADOWMAP_ENABLED " + std::to_string((int)Global.gfx_shadowmap_enabled) + "\n" + + "#define ENVMAP_ENABLED " + std::to_string((int)Global.gfx_envmap_enabled) + "\n" + + "#define MOTIONBLUR_ENABLED " + std::to_string((int)Global.gfx_postfx_motionblur_enabled) + "\n" + + "#define POSTFX_ENABLED " + std::to_string((int)!Global.gfx_skippipeline) + "\n" + + "#define EXTRAEFFECTS_ENABLED " + std::to_string((int)Global.gfx_extraeffects) + "\n" + + "#define USE_GLES " + std::to_string((int)Global.gfx_usegles) + "\n" + + "const uint MAX_LIGHTS = " + std::to_string(MAX_LIGHTS) + "U;\n" + + "const uint MAX_PARAMS = " + std::to_string(MAX_PARAMS) + "U;\n" + + R"STRING( + const uint LIGHT_SPOT = 0U; + const uint LIGHT_POINT = 1U; + const uint LIGHT_DIR = 2U; + + struct light_s + { + vec3 pos; + uint type; + + vec3 dir; + float in_cutoff; + + vec3 color; + float out_cutoff; + + float linear; + float quadratic; + + float intensity; + float ambient; + }; + + layout(std140) uniform light_ubo + { + vec3 ambient; + + vec3 fog_color; + uint lights_count; + + light_s lights[MAX_LIGHTS]; + }; + + layout (std140) uniform model_ubo + { + mat4 modelview; + mat3 modelviewnormal; + vec4 param[MAX_PARAMS]; + + mat4 future; + float opacity; + float emission; + float fog_density; + float alpha_mult; + }; + + layout (std140) uniform scene_ubo + { + mat4 projection; + mat4 lightview; + vec3 scene_extra; + float time; + }; + + )STRING"; +} diff --git a/gl/glsl_common.h b/gl/glsl_common.h new file mode 100644 index 00000000..85e6cc9f --- /dev/null +++ b/gl/glsl_common.h @@ -0,0 +1,10 @@ +#pragma once + +#include "ubo.h" +#include "Globals.h" + +namespace gl +{ + extern std::string glsl_common; + void glsl_common_setup(); +} diff --git a/gl/object.h b/gl/object.h new file mode 100644 index 00000000..eb200889 --- /dev/null +++ b/gl/object.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +namespace gl +{ + class object + { + private: + GLuint id = 0; + + public: + inline operator GLuint() const + { + return id; + } + + inline operator GLuint* const() + { + return &id; + } + + inline operator const GLuint* const() const + { + return &id; + } + + object() = default; + object(const object&) = delete; + object& operator=(const object&) = delete; + }; +} diff --git a/gl/pbo.cpp b/gl/pbo.cpp new file mode 100644 index 00000000..6923e09d --- /dev/null +++ b/gl/pbo.cpp @@ -0,0 +1,65 @@ +#include "stdafx.h" +#include "pbo.h" + +void gl::pbo::request_read(int x, int y, int lx, int ly, int pixsize, GLenum format, GLenum type) +{ + int s = lx * ly * pixsize; + if (s != size) + allocate(PIXEL_PACK_BUFFER, s, GL_STREAM_DRAW); + size = s; + + data_ready = false; + sync.reset(); + + bind(PIXEL_PACK_BUFFER); + glReadPixels(x, y, lx, ly, format, type, 0); + unbind(PIXEL_PACK_BUFFER); + + sync.emplace(); +} + +bool gl::pbo::read_data(int lx, int ly, void *data, int pixsize) +{ + is_busy(); + + if (!data_ready) + return false; + + int s = lx * ly * pixsize; + if (s != size) + return false; + + download(PIXEL_PACK_BUFFER, data, 0, s); + unbind(PIXEL_PACK_BUFFER); + data_ready = false; + + return true; +} + +bool gl::pbo::is_busy() +{ + if (!sync) + return false; + + if (sync->is_signalled()) + { + data_ready = true; + sync.reset(); + return false; + } + + return true; +} + +void* gl::pbo::map(GLuint mode, targets target) +{ + bind(target); + return glMapBuffer(buffer::glenum_target(target), mode); +} + +void gl::pbo::unmap(targets target) +{ + bind(target); + glUnmapBuffer(buffer::glenum_target(target)); + sync.emplace(); +} diff --git a/gl/pbo.h b/gl/pbo.h new file mode 100644 index 00000000..ba804404 --- /dev/null +++ b/gl/pbo.h @@ -0,0 +1,22 @@ +#pragma once + +#include "buffer.h" +#include "fence.h" +#include + +namespace gl { + class pbo : public buffer + { + std::optional sync; + int size = 0; + bool data_ready; + + public: + void request_read(int x, int y, int lx, int ly, int pixsize = 4, GLenum format = GL_RGBA, GLenum type = GL_UNSIGNED_BYTE); + bool read_data(int lx, int ly, void *data, int pixsize = 4); + bool is_busy(); + + void* map(GLuint mode, targets target = PIXEL_UNPACK_BUFFER); + void unmap(targets target = PIXEL_UNPACK_BUFFER); + }; +} diff --git a/gl/postfx.cpp b/gl/postfx.cpp new file mode 100644 index 00000000..6587475a --- /dev/null +++ b/gl/postfx.cpp @@ -0,0 +1,49 @@ +#include "stdafx.h" + +#include "postfx.h" + +std::shared_ptr gl::postfx::vertex; +std::shared_ptr gl::postfx::vao; + +gl::postfx::postfx(const std::string &s) : postfx(shader("postfx_" + s + ".frag")) +{ +} + +gl::postfx::postfx(const shader &s) +{ + if (!vertex) + vertex = std::make_shared("quad.vert"); + if (!vao) + vao = std::make_shared(); + + program.attach(*vertex); + program.attach(s); + program.link(); +} + +void gl::postfx::apply(opengl_texture &src, framebuffer *dst) +{ + apply({&src}, dst); +} + +void gl::postfx::apply(std::vector src, framebuffer *dst) +{ + if (dst) + { + dst->clear(GL_COLOR_BUFFER_BIT); + dst->bind(); + } + else + framebuffer::unbind(); + + program.bind(); + vao->bind(); + + size_t unit = 0; + for (opengl_texture *tex : src) + tex->bind(unit++); + + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} diff --git a/gl/postfx.h b/gl/postfx.h new file mode 100644 index 00000000..85c57a76 --- /dev/null +++ b/gl/postfx.h @@ -0,0 +1,25 @@ +#pragma once + +#include "shader.h" +#include "vao.h" +#include "framebuffer.h" +#include "Texture.h" + +namespace gl +{ + class postfx + { + private: + gl::program program; + static std::shared_ptr vertex; + static std::shared_ptr vao; + + public: + postfx(const std::string &s); + postfx(const shader &s); + + void attach(); + void apply(opengl_texture &src, framebuffer *dst); + void apply(std::vector src, framebuffer *dst); + }; +} diff --git a/gl/query.cpp b/gl/query.cpp new file mode 100644 index 00000000..d98451b8 --- /dev/null +++ b/gl/query.cpp @@ -0,0 +1,65 @@ +#include "stdafx.h" +#include "query.h" +#include "Globals.h" + +gl::query::query(targets target) + : target(target) +{ + glGenQueries(1, *this); +} + +gl::query::~query() +{ + end(); + glDeleteQueries(1, *this); +} + +void gl::query::begin() +{ + if (active_queries[target]) + active_queries[target]->end(); + + glBeginQuery(glenum_target(target), *this); + active_queries[target] = this; +} + +void gl::query::end() +{ + if (active_queries[target] != this) + return; + + glEndQuery(glenum_target(target)); + active_queries[target] = nullptr; +} + +std::optional gl::query::result() +{ + GLuint ready; + glGetQueryObjectuiv(*this, GL_QUERY_RESULT_AVAILABLE, &ready); + int64_t value = 0; + if (ready) { + if (!Global.gfx_usegles) + glGetQueryObjecti64v(*this, GL_QUERY_RESULT, &value); + else + glGetQueryObjectuiv(*this, GL_QUERY_RESULT, reinterpret_cast(&value)); + + return std::optional(value); + } + return std::nullopt; +} + +GLenum gl::query::glenum_target(targets target) +{ + static GLenum mapping[6] = + { + GL_SAMPLES_PASSED, + GL_ANY_SAMPLES_PASSED, + GL_ANY_SAMPLES_PASSED_CONSERVATIVE, + GL_PRIMITIVES_GENERATED, + GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, + GL_TIME_ELAPSED + }; + return mapping[target]; +} + +thread_local gl::query* gl::query::active_queries[6]; diff --git a/gl/query.h b/gl/query.h new file mode 100644 index 00000000..83114092 --- /dev/null +++ b/gl/query.h @@ -0,0 +1,36 @@ +#pragma once + +#include "object.h" + +namespace gl +{ +class query : public object +{ +public: + enum targets + { + SAMPLES_PASSED = 0, + ANY_SAMPLES_PASSED, + ANY_SAMPLES_PASSED_CONSERVATIVE, + PRIMITIVES_GENERATED, + TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, + TIME_ELAPSED + }; + +private: + targets target; + thread_local static query* active_queries[6]; + +protected: + GLenum glenum_target(targets target); + +public: + query(targets target); + ~query(); + + void begin(); + void end(); + + std::optional result(); +}; +} diff --git a/gl/renderbuffer.cpp b/gl/renderbuffer.cpp new file mode 100644 index 00000000..4d896dab --- /dev/null +++ b/gl/renderbuffer.cpp @@ -0,0 +1,27 @@ +#include "stdafx.h" + +#include "renderbuffer.h" + +gl::renderbuffer::renderbuffer() +{ + glGenRenderbuffers(1, *this); +} + +gl::renderbuffer::~renderbuffer() +{ + glDeleteRenderbuffers(1, *this); +} + +void gl::renderbuffer::bind(GLuint id) +{ + glBindRenderbuffer(GL_RENDERBUFFER, id); +} + +void gl::renderbuffer::alloc(GLuint format, int width, int height, int samples) +{ + bind(); + if (samples == 1) + glRenderbufferStorage(GL_RENDERBUFFER, format, width, height); + else + glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, format, width, height); +} diff --git a/gl/renderbuffer.h b/gl/renderbuffer.h new file mode 100644 index 00000000..576cf229 --- /dev/null +++ b/gl/renderbuffer.h @@ -0,0 +1,19 @@ +#pragma once + +#include "object.h" +#include "bindable.h" + +namespace gl +{ + class renderbuffer : public object, public bindable + { + public: + renderbuffer(); + ~renderbuffer(); + + void alloc(GLuint format, int width, int height, int samples = 1); + + static void bind(GLuint id); + using bindable::bind; + }; +} diff --git a/gl/shader.cpp b/gl/shader.cpp new file mode 100644 index 00000000..f4d7f3c7 --- /dev/null +++ b/gl/shader.cpp @@ -0,0 +1,334 @@ +#include "stdafx.h" + +#include +#include +#include "shader.h" +#include "glsl_common.h" +#include "Logs.h" + +inline bool strcend(std::string const &value, std::string const &ending) +{ + if (ending.size() > value.size()) + return false; + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); +} + +std::string gl::shader::read_file(const std::string &filename) +{ + std::stringstream stream; + std::ifstream f; + f.exceptions(std::ifstream::badbit); + + f.open("shaders/" + filename); + stream << f.rdbuf(); + f.close(); + + std::string str = stream.str(); + + return str; +} + +void gl::shader::expand_includes(std::string &str) +{ + size_t start_pos = 0; + + std::string magic = "#include"; + while ((start_pos = str.find(magic, start_pos)) != str.npos) + { + size_t fp = str.find('<', start_pos); + size_t fe = str.find('>', start_pos); + if (fp == str.npos || fe == str.npos) + return; + + std::string filename = str.substr(fp + 1, fe - fp - 1); + std::string content; + if (filename != "common") + content = read_file(filename); + else + content = glsl_common; + + str.replace(start_pos, fe - start_pos + 1, content); + } +} + +std::unordered_map gl::shader::components_mapping = +{ + { "R", components_e::R }, + { "RG", components_e::RG }, + { "RGB", components_e::RGB }, + { "RGBA", components_e::RGBA }, + { "sRGB", components_e::sRGB }, + { "sRGB_A", components_e::sRGB_A } +}; + +std::unordered_map gl::shader::defaultparams_mapping = +{ + { "required", defaultparam_e::required }, + { "nan", defaultparam_e::nan }, + { "zero", defaultparam_e::zero }, + { "one", defaultparam_e::one }, + { "ambient", defaultparam_e::ambient }, + { "diffuse", defaultparam_e::diffuse }, + { "specular", defaultparam_e::specular } +}; + +void gl::shader::process_source(std::string &str) +{ + expand_includes(str); + parse_texture_entries(str); + parse_param_entries(str); +} + +void gl::shader::parse_texture_entries(std::string &str) +{ + size_t start_pos = 0; + + std::string magic = "#texture"; + while ((start_pos = str.find(magic, start_pos)) != str.npos) + { + size_t fp = str.find('(', start_pos); + size_t fe = str.find(')', start_pos); + if (fp == str.npos || fe == str.npos) + return; + + std::istringstream ss(str.substr(fp + 1, fe - fp - 1)); + std::string token; + + std::string name; + texture_entry conf; + + size_t arg = 0; + while (std::getline(ss, token, ',')) + { + std::istringstream token_ss(token); + if (arg == 0) + token_ss >> name; + else if (arg == 1) + token_ss >> conf.id; + else if (arg == 2) + { + std::string comp; + token_ss >> comp; + if (components_mapping.find(comp) == components_mapping.end()) + log_error("unknown components: " + comp); + else + conf.components = components_mapping[comp]; + } + arg++; + } + + if (arg == 3) + { + if (name.empty()) + log_error("empty name"); + else if (conf.id >= gl::MAX_TEXTURES) + log_error("invalid texture binding: " + std::to_string(conf.id)); + else + texture_conf.emplace(std::make_pair(name, conf)); + } + else + log_error("invalid argument count to #texture"); + + str.erase(start_pos, fe - start_pos + 1); + } +} + +void gl::shader::parse_param_entries(std::string &str) +{ + size_t start_pos = 0; + + std::string magic = "#param"; + while ((start_pos = str.find(magic, start_pos)) != str.npos) + { + size_t fp = str.find('(', start_pos); + size_t fe = str.find(')', start_pos); + if (fp == str.npos || fe == str.npos) + return; + + std::istringstream ss(str.substr(fp + 1, fe - fp - 1)); + std::string token; + + std::string name; + param_entry conf; + + size_t arg = 0; + while (std::getline(ss, token, ',')) + { + std::istringstream token_ss(token); + if (arg == 0) + token_ss >> name; + else if (arg == 1) + token_ss >> conf.location; + else if (arg == 2) + token_ss >> conf.offset; + else if (arg == 3) + token_ss >> conf.size; + else if (arg == 4) + { + std::string tok; + token_ss >> tok; + if (defaultparams_mapping.find(tok) == defaultparams_mapping.end()) + log_error("unknown param default: " + tok); + conf.defaultparam = defaultparams_mapping[tok]; + } + arg++; + } + + if (arg == 5) + { + if (name.empty()) + log_error("empty name"); + else if (conf.location >= gl::MAX_PARAMS) + log_error("invalid param binding: " + std::to_string(conf.location)); + else if (conf.offset > 3) + log_error("invalid offset: " + std::to_string(conf.offset)); + else if (conf.offset + conf.size > 4) + log_error("invalid size: " + std::to_string(conf.size)); + else + param_conf.emplace(std::make_pair(name, conf)); + } + else + log_error("invalid argument count to #param"); + + str.erase(start_pos, fe - start_pos + 1); + } +} + +void gl::shader::log_error(const std::string &str) +{ + ErrorLog("bad shader: " + name + ": " + str, logtype::shader); +} + +gl::shader::shader(const std::string &filename) +{ + name = filename; + + GLuint type; + if (strcend(filename, ".vert")) + type = GL_VERTEX_SHADER; + else if (strcend(filename, ".frag")) + type = GL_FRAGMENT_SHADER; + else if (strcend(filename, ".geom")) + type = GL_GEOMETRY_SHADER; + else + throw shader_exception("unknown shader " + filename); + + std::string str; + if (!Global.gfx_usegles) + { + str += "#version 330 core\n"; + } + else + { + if (GLAD_GL_ES_VERSION_3_1) { + str += "#version 310 es\n"; + if (type == GL_GEOMETRY_SHADER) + str += "#extension GL_EXT_geometry_shader : require\n"; + } else { + str += "#version 300 es\n"; + } + str += "precision highp float;\n"; + str += "precision highp sampler2DShadow;\n"; + } + str += "vec4 FBOUT(vec4 x) { return " + (Global.gfx_shadergamma ? std::string("vec4(pow(x.rgb, vec3(1.0 / 2.2)), x.a)") : std::string("x")) + "; }\n"; + + str += read_file(filename); + process_source(str); + + const GLchar *cstr = str.c_str(); + + if (!cstr[0]) + throw shader_exception("cannot read shader: " + filename); + + **this = glCreateShader(type); + glShaderSource(*this, 1, &cstr, 0); + glCompileShader(*this); + + GLint status; + glGetShaderiv(*this, GL_COMPILE_STATUS, &status); + if (!status) + { + GLchar info[512]; + glGetShaderInfoLog(*this, 512, 0, info); + std::cerr << std::string(info) << std::endl; + throw shader_exception("failed to compile " + filename + ": " + std::string(info)); + } +} + +gl::shader::~shader() +{ + glDeleteShader(*this); +} + +void gl::program::init() +{ + bind(); + + for (auto it : texture_conf) + { + shader::texture_entry &e = it.second; + GLuint loc = glGetUniformLocation(*this, it.first.c_str()); + glUniform1i(loc, e.id); + } + + glUniform1i(glGetUniformLocation(*this, "shadowmap"), MAX_TEXTURES + 0); + glUniform1i(glGetUniformLocation(*this, "envmap"), MAX_TEXTURES + 1); + + GLuint index; + + if ((index = glGetUniformBlockIndex(*this, "scene_ubo")) != GL_INVALID_INDEX) + glUniformBlockBinding(*this, 0, index); + + if ((index = glGetUniformBlockIndex(*this, "model_ubo")) != GL_INVALID_INDEX) + glUniformBlockBinding(*this, 1, index); + + if ((index = glGetUniformBlockIndex(*this, "light_ubo")) != GL_INVALID_INDEX) + glUniformBlockBinding(*this, 2, index); +} + +gl::program::program() +{ + **this = glCreateProgram(); +} + +gl::program::program(std::vector> shaders) : program() +{ + for (const gl::shader &s : shaders) + attach(s); + link(); +} + +void gl::program::attach(const gl::shader &s) +{ + for (auto it : s.texture_conf) + texture_conf.emplace(std::make_pair(it.first, std::move(it.second))); + for (auto it : s.param_conf) + param_conf.emplace(std::make_pair(it.first, std::move(it.second))); + glAttachShader(*this, *s); +} + +void gl::program::link() +{ + glLinkProgram(*this); + + GLint status; + glGetProgramiv(*this, GL_LINK_STATUS, &status); + if (!status) + { + GLchar info[512]; + glGetProgramInfoLog(*this, 512, 0, info); + throw shader_exception("failed to link program: " + std::string(info)); + } + + init(); +} + +gl::program::~program() +{ + glDeleteProgram(*this); +} + +void gl::program::bind(GLuint i) +{ + glUseProgram(i); +} diff --git a/gl/shader.h b/gl/shader.h new file mode 100644 index 00000000..0430d450 --- /dev/null +++ b/gl/shader.h @@ -0,0 +1,96 @@ +#pragma once + +#include +#include +#include + +#include "object.h" +#include "bindable.h" + +namespace gl +{ + class shader_exception : public std::runtime_error + { + using runtime_error::runtime_error; + }; + + class shader : public object + { + public: + shader(const std::string &filename); + ~shader(); + + enum class components_e + { + R = GL_RED, + RG = GL_RG, + RGB = GL_RGB, + RGBA = GL_RGBA, + sRGB = GL_SRGB, + sRGB_A = GL_SRGB_ALPHA + }; + + struct texture_entry + { + size_t id; + components_e components; + }; + + enum class defaultparam_e + { + required, + nan, + zero, + one, + ambient, + diffuse, + specular + }; + + struct param_entry + { + size_t location; + size_t offset; + size_t size; + defaultparam_e defaultparam; + }; + + std::unordered_map texture_conf; + std::unordered_map param_conf; + std::string name; + + private: + void process_source(std::string &str); + + void expand_includes(std::string &str); + void parse_texture_entries(std::string &str); + void parse_param_entries(std::string &str); + + std::string read_file(const std::string &filename); + + static std::unordered_map components_mapping; + static std::unordered_map defaultparams_mapping; + + void log_error(const std::string &str); + }; + + class program : public object, public bindable + { + public: + program(); + program(std::vector>); + ~program(); + + using bindable::bind; + static void bind(GLuint i); + + void attach(const shader &); + void link(); + + std::unordered_map texture_conf; + std::unordered_map param_conf; + + private: + void init(); + }; +} diff --git a/gl/ubo.cpp b/gl/ubo.cpp new file mode 100644 index 00000000..1ef45198 --- /dev/null +++ b/gl/ubo.cpp @@ -0,0 +1,19 @@ +#include "stdafx.h" +#include "ubo.h" + +gl::ubo::ubo(int size, int idx, GLenum hint) +{ + allocate(buffer::UNIFORM_BUFFER, size, hint); + index = idx; + bind_uniform(); +} + +void gl::ubo::bind_uniform() +{ + bind_base(buffer::UNIFORM_BUFFER, index); +} + +void gl::ubo::update(const uint8_t *data, int offset, int size) +{ + upload(buffer::UNIFORM_BUFFER, data, offset, size); +} diff --git a/gl/ubo.h b/gl/ubo.h new file mode 100644 index 00000000..c8ce5167 --- /dev/null +++ b/gl/ubo.h @@ -0,0 +1,113 @@ +#pragma once + +#include "object.h" +#include "bindable.h" +#include "buffer.h" + +#define UBS_PAD(x) uint8_t PAD[x] + +namespace gl +{ + class ubo : public buffer + { + int index; + + public: + ubo(int size, int index, GLenum hint = GL_DYNAMIC_DRAW); + + void bind_uniform(); + + void update(const uint8_t *data, int offset, int size); + template void update(const T &data, size_t offset = 0) + { + update(reinterpret_cast(&data), offset, sizeof(data)); + } + }; + + // layout std140 + // structs must match with GLSL + // ordered to minimize padding + +#pragma pack(push, 1) + + const size_t MAX_TEXTURES = 8; + const size_t ENVMAP_SIZE = 1024; + + struct scene_ubs + { + glm::mat4 projection; + glm::mat4 lightview; + glm::vec3 scene_extra; + float time; + }; + + static_assert(sizeof(scene_ubs) == 144, "bad size of ubs"); + + const size_t MAX_PARAMS = 3; + + struct model_ubs + { + glm::mat4 modelview; + glm::mat3x4 modelviewnormal; + glm::vec4 param[MAX_PARAMS]; + + glm::mat4 future; + float opacity; + float emission; + float fog_density; + float alpha_mult; + UBS_PAD(4); + + void set_modelview(const glm::mat4 &mv) + { + modelview = mv; + modelviewnormal = glm::mat3x4(glm::mat3(glm::transpose(glm::inverse(mv)))); + } + }; + + static_assert(sizeof(model_ubs) == 196 + 16 * MAX_PARAMS, "bad size of ubs"); + + struct light_element_ubs + { + enum type_e + { + SPOT = 0, + POINT, + DIR + }; + + glm::vec3 pos; + type_e type; + + glm::vec3 dir; + float in_cutoff; + + glm::vec3 color; + float out_cutoff; + + float linear; + float quadratic; + + float intensity; + float ambient; + }; + + static_assert(sizeof(light_element_ubs) == 64, "bad size of ubs"); + + const size_t MAX_LIGHTS = 8; + + struct light_ubs + { + glm::vec3 ambient; + UBS_PAD(4); + + glm::vec3 fog_color; + uint32_t lights_count; + + light_element_ubs lights[MAX_LIGHTS]; + }; + + static_assert(sizeof(light_ubs) == 32 + sizeof(light_element_ubs) * MAX_LIGHTS, "bad size of ubs"); + +#pragma pack(pop) +} diff --git a/gl/vao.cpp b/gl/vao.cpp new file mode 100644 index 00000000..fd582ae3 --- /dev/null +++ b/gl/vao.cpp @@ -0,0 +1,85 @@ +#include "stdafx.h" +#include "vao.h" + +bool gl::vao::use_vao = true; + +gl::vao::vao() +{ + if (!use_vao) + return; + + glGenVertexArrays(1, *this); +} + +gl::vao::~vao() +{ + if (!use_vao) + return; + + unbind(); + glDeleteVertexArrays(1, *this); +} + +void gl::vao::setup_attrib(gl::buffer &buffer, int attrib, int size, int type, int stride, int offset) +{ + if (use_vao) { + bind(); + buffer.bind(buffer::ARRAY_BUFFER); + glVertexAttribPointer(attrib, size, type, GL_FALSE, stride, reinterpret_cast(offset)); + glEnableVertexAttribArray(attrib); + } + else { + params.push_back({buffer, attrib, size, type, stride, offset}); + active = nullptr; + } +} + +void gl::vao::setup_ebo(buffer &e) +{ + if (use_vao) { + bind(); + e.bind(buffer::ELEMENT_ARRAY_BUFFER); + } + else { + ebo = &e; + active = nullptr; + } +} + +void gl::vao::bind() +{ + if (active == this) + return; + active = this; + + if (use_vao) { + glBindVertexArray(*this); + } + else { + for (attrib_params ¶m : params) { + param.buffer.bind(gl::buffer::ARRAY_BUFFER); + glVertexAttribPointer(param.attrib, param.size, param.type, GL_FALSE, param.stride, reinterpret_cast(param.offset)); + glEnableVertexAttribArray(param.attrib); + } + + for (size_t i = params.size(); i < 4; i++) + glDisableVertexAttribArray(i); + + if (ebo) + ebo->bind(gl::buffer::ELEMENT_ARRAY_BUFFER); + else + gl::buffer::unbind(gl::buffer::ELEMENT_ARRAY_BUFFER); + } +} + +void gl::vao::unbind() +{ + active = nullptr; + if (use_vao) { + glBindVertexArray(0); + } + else { + for (size_t i = 0; i < 4; i++) + glDisableVertexAttribArray(i); + } +} diff --git a/gl/vao.h b/gl/vao.h new file mode 100644 index 00000000..a4415d03 --- /dev/null +++ b/gl/vao.h @@ -0,0 +1,37 @@ +#pragma once + +#include "object.h" +#include "bindable.h" +#include "buffer.h" + +namespace gl +{ + class vao : object, public bindable + { + struct attrib_params { + // TBD: should be shared_ptr? (when buffer is destroyed by owner VAO could still potentially exist) + gl::buffer &buffer; + + int attrib; + int size; + int type; + int stride; + int offset; + }; + buffer *ebo = nullptr; + + std::vector params; + + public: + vao(); + ~vao(); + + void setup_attrib(buffer &buffer, int attrib, int size, int type, int stride, int offset); + void setup_ebo(buffer &ebo); + + void bind(); + static void unbind(); + + static bool use_vao; + }; +} diff --git a/maszyna.vcxproj.filters b/maszyna.vcxproj.filters index e61198c3..c337709d 100644 --- a/maszyna.vcxproj.filters +++ b/maszyna.vcxproj.filters @@ -58,14 +58,23 @@ {77356e25-abc5-4f1c-9caf-6cf554a65770} + + {4fcccdfc-d0d4-45a9-9603-b13d8dfe345c} + + + {8b47594a-f06a-4c9b-9aef-70f6ec24a288} + + + {b8dcf22f-cddb-4d1f-a2fa-8c54e5da4a39} + + + {dd00198e-a316-4bcc-a4d3-916c8dcfe08f} + Source Files - - Source Files - Source Files @@ -78,9 +87,6 @@ Source Files - - Source Files - Source Files @@ -90,9 +96,6 @@ Source Files - - Source Files - Source Files @@ -102,15 +105,9 @@ Source Files - - Source Files - Source Files - - Source Files - Source Files @@ -126,15 +123,9 @@ Source Files - - Source Files - Source Files - - Source Files - Source Files @@ -174,15 +165,9 @@ Source Files - - Source Files - Source Files - - Source Files - Source Files @@ -192,30 +177,18 @@ Source Files - - Source Files - Source Files - - Source Files - Source Files Source Files - - Source Files - Source Files - - Source Files - Source Files @@ -336,9 +309,6 @@ Source Files - - Source Files - Source Files\imgui @@ -351,23 +321,119 @@ Source Files + + Source Files\gfx\gl + + + Source Files\gfx\gl + + + Source Files\gfx\gl + + + Source Files\gfx\gl + + + Source Files\gfx\gl + + + Source Files\gfx\gl + + + Source Files\gfx\gl + + + Source Files\gfx\gl + + + Source Files\gfx\gl + + + Source Files\gfx\gl + + + Source Files\gfx\gl + + + Source Files\gfx\gl + + + Source Files\math + + + Source Files\math + + + Source Files\math + + + Source Files\gfx + - Source Files + Source Files\gfx - - Source Files + + Source Files\gfx - - Source Files + + Source Files\gfx + + + Source Files\gfx - Source Files + Source Files\gfx + + + Source Files\gfx + + + Source Files\gfx + + + Source Files\gfx + + + Source Files\gfx - Source Files + Source Files\gfx + + + Source Files\gfx - Source Files + Source Files\gfx + + + Source Files\gfx + + + Source Files\gfx + + + Source Files\gfx + + + Source Files\gfx + + + Source Files\gfx\gl + + + Source Files\gfx + + + Source Files\gfx + + + Source Files\gfx + + + Source Files\gfx + + + Source Files\gfx @@ -638,9 +704,6 @@ Header Files\gfx - - Header Files\gfx - Header Files\gfx @@ -677,6 +740,66 @@ Header Files + + Header Files\gfx\gl + + + Header Files\gfx\gl + + + Header Files\gfx\gl + + + Header Files\gfx\gl + + + Header Files\gfx\gl + + + Header Files\gfx\gl + + + Header Files\gfx\gl + + + Header Files\gfx\gl + + + Header Files\gfx\gl + + + Header Files\gfx\gl + + + Header Files\gfx\gl + + + Header Files\gfx\gl + + + Header Files\gfx\gl + + + Header Files\gfx\gl + + + Header Files\gfx + + + Header Files\gfx + + + Header Files\gfx + + + Header Files + + + Header Files\gfx + + + Header Files\gfx + diff --git a/material.cpp b/material.cpp index a868dcdd..2dfeb068 100644 --- a/material.cpp +++ b/material.cpp @@ -15,28 +15,172 @@ http://mozilla.org/MPL/2.0/. #include "utilities.h" #include "sn_utils.h" #include "Globals.h" +#include "Logs.h" + +opengl_material::opengl_material() +{ + for (size_t i = 0; i < params.size(); i++) + params[i] = glm::vec4(std::numeric_limits::quiet_NaN()); +} bool opengl_material::deserialize( cParser &Input, bool const Loadnow ) { + parse_info = std::make_unique(); bool result { false }; while( true == deserialize_mapping( Input, 0, Loadnow ) ) { result = true; // once would suffice but, eh } - has_alpha = ( - texture1 != null_handle ? - GfxRenderer->Texture( texture1 ).has_alpha : - false ); - return result; } +void opengl_material::log_error(const std::string &str) +{ + ErrorLog("bad material: " + name + ": " + str, logtype::material); +} + +std::map texture_bindings { + + { "diffuse", 0 }, + { "normal", 1 } +}; + +void opengl_material::finalize(bool Loadnow) +{ + if (parse_info) + { + for (auto it : parse_info->tex_mapping) + { + std::string key = it.first; + std::string value = it.second.name; + + if (key.size() > 0 && key[0] != '_') + { + size_t num = std::stoi(key) - 1; + if (num < gl::MAX_TEXTURES) + textures[num] = GfxRenderer->Fetch_Texture(value, Loadnow); + else + log_error("invalid texture binding: " + std::to_string(num)); + } + else if (key.size() > 2) + { + key.erase(0, 1); + key.pop_back(); + std::map::iterator lookup; + if( shader && shader->texture_conf.find( key ) != shader->texture_conf.end() ) { + textures[ shader->texture_conf[ key ].id ] = GfxRenderer->Fetch_Texture( value, Loadnow ); + } + else if( ( shader == nullptr ) + && ( lookup = texture_bindings.find( key ) ) != texture_bindings.end() ) { + textures[ lookup->second ] = GfxRenderer->Fetch_Texture( value, Loadnow ); + } + else { + log_error( "unknown texture binding: " + key ); + } + } + else + log_error("unrecognized texture binding: " + key); + } + + if (!shader) + { + if (textures[0] == null_handle) + { + log_error("shader not specified, assuming \"default_0\""); + shader = GfxRenderer->Fetch_Shader("default_0"); + } + else if (textures[1] == null_handle) + { + log_error("shader not specified, assuming \"default_1\""); + shader = GfxRenderer->Fetch_Shader("default_1"); + } + else if (textures[2] == null_handle) + { + log_error("shader not specified, assuming \"default_2\""); + shader = GfxRenderer->Fetch_Shader("default_2"); + } + } + + if (!shader) + return; + + for (auto it : parse_info->param_mapping) + { + std::string key = it.first; + glm::vec4 value = it.second.data; + + if (key.size() > 1 && key[0] != '_') + { + size_t num = std::stoi(key) - 1; + if (num < gl::MAX_PARAMS) + params[num] = value; + else + log_error("invalid param binding: " + std::to_string(num)); + } + else if (key.size() > 2) + { + key.erase(0, 1); + key.pop_back(); + if (shader->param_conf.find(key) != shader->param_conf.end()) + { + gl::shader::param_entry entry = shader->param_conf[key]; + for (size_t i = 0; i < entry.size; i++) + params[entry.location][entry.offset + i] = value[i]; + } + else + log_error("unknown param binding: " + key); + } + else + log_error("unrecognized param binding: " + key); + } + + parse_info.reset(); + } + + if (!shader) + return; + + for (auto it : shader->param_conf) + { + gl::shader::param_entry entry = it.second; + if (std::isnan(params[entry.location][entry.offset])) + { + float value = std::numeric_limits::quiet_NaN(); + if (entry.defaultparam == gl::shader::defaultparam_e::one) + value = 1.0f; + else if (entry.defaultparam == gl::shader::defaultparam_e::zero) + value = 0.0f; + else if (entry.defaultparam == gl::shader::defaultparam_e::required) + log_error("unspecified required param: " + it.first); + else if (entry.defaultparam != gl::shader::defaultparam_e::nan) + { + params_state.push_back(entry); + continue; + } + + for (size_t i = 0; i < entry.size; i++) + params[entry.location][entry.offset + i] = value; + } + } + + for (auto it : shader->texture_conf) + { + gl::shader::texture_entry &entry = it.second; + texture_handle handle = textures[entry.id]; + if (handle) + GfxRenderer->Texture(handle).set_components_hint((GLint)entry.components); + else + log_error("missing texture: " + it.first); + } +} + // imports member data pair from the config file bool opengl_material::deserialize_mapping( cParser &Input, int const Priority, bool const Loadnow ) { + // NOTE: comma can be part of legacy file names, so we don't treat it as a separator here - std::string const key { Input.getToken( true, "\n\r\t ;[]" ) }; + auto key { Input.getToken( true, "\n\r\t ;[]" ) }; // key can be an actual key or block end if( ( true == key.empty() ) || ( key == "}" ) ) { return false; } @@ -59,24 +203,69 @@ opengl_material::deserialize_mapping( cParser &Input, int const Priority, bool c ; // all work is done in the header } } - else if( ( key == "texture1:" ) - || ( key == "texture_diffuse:" ) ) { - auto const value { deserialize_random_set( Input ) }; - if( ( texture1 == null_handle ) - || ( Priority > priority1 ) ) { - texture1 = GfxRenderer->Fetch_Texture( value, Loadnow ); - priority1 = Priority; + + else if (key.compare(0, 7, "texture") == 0) { + key.erase(0, 7); + + auto value { deserialize_random_set( Input ) }; + replace_slashes( value ); + auto it = parse_info->tex_mapping.find(key); + if (it == parse_info->tex_mapping.end()) + parse_info->tex_mapping.emplace(std::make_pair(key, parse_info_s::tex_def({ value, Priority }))); + else if (Priority > it->second.priority) + { + parse_info->tex_mapping.erase(it); + parse_info->tex_mapping.emplace(std::make_pair(key, parse_info_s::tex_def({ value, Priority }))); } } - else if( ( key == "texture2:" ) - || ( key == "texture_normalmap:" ) ) { - auto const value { deserialize_random_set( Input ) }; - if( ( texture2 == null_handle ) - || ( Priority > priority2 ) ) { - texture2 = GfxRenderer->Fetch_Texture( value, Loadnow ); - priority2 = Priority; + else if (key.compare(0, 5, "param") == 0) { + key.erase(0, 5); + + std::string value = Input.getToken( true, "\n\r\t;" ); + std::istringstream stream(value); + glm::vec4 data; + stream >> data.r; + stream >> data.g; + stream >> data.b; + stream >> data.a; + + auto it = parse_info->param_mapping.find(key); + if (it == parse_info->param_mapping.end()) + parse_info->param_mapping.emplace(std::make_pair(key, parse_info_s::param_def({ data, Priority }))); + else if (Priority > it->second.priority) + { + parse_info->param_mapping.erase(it); + parse_info->param_mapping.emplace(std::make_pair(key, parse_info_s::param_def({ data, Priority }))); } } + else if (key == "shader:" && + (!shader || Priority > m_shader_priority)) + { + try + { + std::string value = deserialize_random_set( Input ); + shader = GfxRenderer->Fetch_Shader(value); + m_shader_priority = Priority; + } + catch (gl::shader_exception const &e) + { + log_error("invalid shader: " + std::string(e.what())); + } + } + else if (key == "opacity:" && + Priority > m_opacity_priority) + { + std::string value = deserialize_random_set( Input ); + opacity = std::stof(value); //m7t: handle exception + m_opacity_priority = Priority; + } + else if (key == "selfillum:" && + Priority > m_selfillum_priority) + { + std::string value = deserialize_random_set( Input ); + selfillum = std::stof(value); //m7t: handle exception + m_selfillum_priority = Priority; + } else if( key == "size:" ) { Input.getTokens( 2 ); Input @@ -109,6 +298,22 @@ opengl_material::deserialize_mapping( cParser &Input, int const Priority, bool c return true; // return value marks a key: value pair was extracted, nothing about whether it's recognized } +float opengl_material::get_or_guess_opacity() const { + + if (!std::isnan(opacity)) + return opacity; + + if (textures[0] != null_handle) + { + auto const &tex = GfxRenderer->Texture(textures[0]); + if (tex.has_alpha) + return 0.0f; + else + return 0.5f; + } + + return 0.0f; +} // 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 @@ -169,19 +374,35 @@ material_manager::create( std::string const &Filename, bool const Loadnow ) { // if there's no .mat file, this can be either autogenerated texture, // or legacy method of referring just to diffuse texture directly. // wrap basic material around it in either case - material.texture1 = GfxRenderer->Fetch_Texture( Filename, Loadnow ); - if( material.texture1 != null_handle ) { - // use texture path and name to tell the newly created materials apart - material.name = GfxRenderer->Texture( material.texture1 ).name; - material.has_alpha = GfxRenderer->Texture( material.texture1 ).has_alpha; + material.textures[0] = GfxRenderer->Fetch_Texture( Filename, Loadnow ); + if( material.textures[0] != null_handle ) + { + // use texture path and name to tell the newly created materials apart + material.name = GfxRenderer->Texture( material.textures[0] ).name; + + // material would attach default shader anyway, but it would spit to error log + try + { + material.shader = GfxRenderer->Fetch_Shader("default_1"); + } + catch (gl::shader_exception const &e) + { + ErrorLog("invalid shader: " + std::string(e.what())); + } } } - if( false == material.name.empty() ) { - // if we have material name it means resource was processed succesfully - materialhandle = m_materials.size(); - m_materials.emplace_back( material ); - m_materialmappings.emplace( material.name, materialhandle ); + if( false == material.name.empty() ) { + // if we have material name and shader it means resource was processed succesfully + try { + material.finalize(Loadnow); + materialhandle = m_materials.size(); + m_materialmappings.emplace( material.name, materialhandle ); + m_materials.emplace_back( std::move(material) ); + } catch (gl::shader_exception const &e) { + ErrorLog("invalid shader: " + std::string(e.what())); + m_materialmappings.emplace( filename, materialhandle ); + } } else { // otherwise record our failure to process the resource, to speed up subsequent attempts @@ -222,4 +443,4 @@ material_manager::find_on_disk( std::string const &Materialname ) const { { ".mat" } ) ); } -//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- \ No newline at end of file diff --git a/material.h b/material.h index cba771aa..cbf4f11c 100644 --- a/material.h +++ b/material.h @@ -11,40 +11,69 @@ http://mozilla.org/MPL/2.0/. #include "Classes.h" #include "Texture.h" +#include "gl/shader.h" +#include "gl/ubo.h" typedef int material_handle; // a collection of parameters for the rendering setup. -// for modern opengl this translates to set of attributes for the active shaders, -// for legacy opengl this is basically just texture(s) assigned to geometry +// for modern opengl this translates to set of attributes for shaders struct opengl_material { + std::array textures = { null_handle }; + std::array params; + std::vector params_state; - texture_handle texture1 { null_handle }; // primary texture, typically diffuse+apha - texture_handle texture2 { null_handle }; // secondary texture, typically normal+reflection + std::shared_ptr shader; + float opacity = std::numeric_limits::quiet_NaN(); + float selfillum = std::numeric_limits::quiet_NaN(); - bool has_alpha { false }; // alpha state, calculated from presence of alpha in texture1 std::string name; glm::vec2 size { -1.f, -1.f }; // 'physical' size of bound texture, in meters // constructors - opengl_material() = default; + opengl_material(); // methods bool 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 ); } private: // methods - // imports member data pair from the config file, overriding existing parameter values of lower priority + // imports member data pair from the config file bool deserialize_mapping( cParser &Input, int const Priority, bool const Loadnow ); + void log_error(const std::string &str); // extracts name of the sound file from provided data stream std::string deserialize_filename( cParser &Input ); // members - int priority1 { -1 }; // priority of last loaded primary texture - int priority2 { -1 }; // priority of last loaded secondary texture + // priorities for textures, shader, opacity + int m_shader_priority = -1; + int m_opacity_priority = -1; + int m_selfillum_priority = -1; + + struct parse_info_s + { + struct tex_def + { + std::string name; + int priority; + }; + struct param_def + { + glm::vec4 data; + int priority; + }; + std::unordered_map tex_mapping; + std::unordered_map param_mapping; + }; + std::unique_ptr parse_info; }; class material_manager { @@ -56,6 +85,8 @@ public: create( std::string const &Filename, bool const Loadnow ); opengl_material const & material( material_handle const Material ) const { return m_materials[ Material ]; } + opengl_material & + material( material_handle const Material ) { return m_materials[ Material ]; } private: // types @@ -71,7 +102,6 @@ private: // members: material_sequence m_materials; index_map m_materialmappings; - }; -//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- \ No newline at end of file diff --git a/moon.h b/moon.h index eb59a1a7..92fd7243 100644 --- a/moon.h +++ b/moon.h @@ -1,8 +1,6 @@ #pragma once #include "windows.h" -#include "GL/glew.h" -#include "GL/wglew.h" // TODO: sun and moon share code as celestial bodies, we could make a base class out of it diff --git a/opengl33light.cpp b/opengl33light.cpp new file mode 100644 index 00000000..5be0cf98 --- /dev/null +++ b/opengl33light.cpp @@ -0,0 +1,22 @@ +/* +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 "opengl33light.h" + +void opengl33_light::apply_intensity(float const Factor) { + + factor = Factor; +} + +void opengl33_light::apply_angle() {} + + +//--------------------------------------------------------------------------- diff --git a/opengl33light.h b/opengl33light.h new file mode 100644 index 00000000..6f363061 --- /dev/null +++ b/opengl33light.h @@ -0,0 +1,28 @@ +/* +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 "light.h" + +struct opengl33_light : public basic_light { + + GLuint id{(GLuint)-1}; + + float factor; + + void apply_intensity(float const Factor = 1.0f); + void apply_angle(); + + opengl33_light &operator=(basic_light const &Right) { + basic_light::operator=(Right); + return *this; } +}; + +//--------------------------------------------------------------------------- diff --git a/opengl33particles.cpp b/opengl33particles.cpp new file mode 100644 index 00000000..eb019212 --- /dev/null +++ b/opengl33particles.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 "opengl33particles.h" + +#include "particles.h" +#include "openglcamera.h" +#include "simulation.h" + +std::vector> const billboard_vertices { + + { { -0.5f, -0.5f, 0.f }, { 0.f, 0.f } }, + { { 0.5f, -0.5f, 0.f }, { 1.f, 0.f } }, + { { 0.5f, 0.5f, 0.f }, { 1.f, 1.f } }, + { { -0.5f, -0.5f, 0.f }, { 0.f, 0.f } }, + { { 0.5f, 0.5f, 0.f }, { 1.f, 1.f } }, + { { -0.5f, 0.5f, 0.f }, { 0.f, 1.f } }, +}; + +void +opengl33_particles::update( opengl_camera const &Camera ) { + + if (!Global.Smoke) + return; + + m_particlevertices.clear(); + // build a list of visible smoke sources + // NOTE: arranged by distance to camera, if we ever need sorting and/or total amount cap-based culling + std::multimap sources; + + for( auto const &source : simulation::Particles.sequence() ) { + if( false == Camera.visible( source.area() ) ) { continue; } + // NOTE: the distance is negative when the camera is inside the source's bounding area + sources.emplace( + static_cast( glm::length( Camera.position() - source.area().center ) - source.area().radius ), + source ); + } + + if( true == sources.empty() ) { return; } + + // build billboard data for particles from visible sources + auto const camerarotation { glm::mat3( Camera.modelview() ) }; + particle_vertex vertex; + for( auto const &source : sources ) { + + auto const particlecolor { + glm::clamp( + source.second.color() + * ( glm::vec3 { Global.DayLight.ambient } + 0.35f * glm::vec3{ Global.DayLight.diffuse } ), + 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 + auto const sizestep { 256.0 * billboard_vertices.size() }; + m_particlevertices.reserve( + sizestep * std::ceil( m_particlevertices.size() + ( particles.size() * billboard_vertices.size() ) / sizestep ) ); + for( auto const &particle : particles ) { + // TODO: particle color support + 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); + + auto const offset { glm::vec3{ particle.position - Camera.position() } }; + auto const rotation { glm::angleAxis( particle.rotation, glm::vec3{ 0.f, 0.f, 1.f } ) }; + + for( auto const &billboardvertex : billboard_vertices ) { + vertex.position = offset + ( rotation * billboardvertex.first * particle.size ) * camerarotation; + vertex.texture = billboardvertex.second; + + m_particlevertices.emplace_back( vertex ); + } + } + } + + // ship the billboard data to the gpu: + // make sure we have enough room... + if( m_buffercapacity < m_particlevertices.size() ) { + m_buffercapacity = m_particlevertices.size(); + if (!m_buffer) + m_buffer.emplace(); + + m_buffer->allocate(gl::buffer::ARRAY_BUFFER, + m_buffercapacity * sizeof(particle_vertex), GL_STREAM_DRAW); + } + + if (m_buffer) { + // ...send the data... + m_buffer->upload(gl::buffer::ARRAY_BUFFER, + m_particlevertices.data(), 0, m_particlevertices.size() * sizeof(particle_vertex)); + } +} + +void +opengl33_particles::render() { + + if (!Global.Smoke) + return; + + if( m_buffercapacity == 0 ) { return; } + if( m_particlevertices.empty() ) { return; } + + if (!m_vao) { + m_vao.emplace(); + + m_vao->setup_attrib(*m_buffer, 0, 3, GL_FLOAT, sizeof(particle_vertex), 0); + m_vao->setup_attrib(*m_buffer, 1, 4, GL_FLOAT, sizeof(particle_vertex), 12); + m_vao->setup_attrib(*m_buffer, 2, 2, GL_FLOAT, sizeof(particle_vertex), 28); + + m_buffer->unbind(gl::buffer::ARRAY_BUFFER); + m_vao->unbind(); + } + + if (!m_shader) { + gl::shader vert("smoke.vert"); + gl::shader frag("smoke.frag"); + gl::program *prog = new gl::program({vert, frag}); + m_shader = std::unique_ptr(prog); + } + + m_buffer->bind(gl::buffer::ARRAY_BUFFER); + m_shader->bind(); + m_vao->bind(); + + glDrawArrays(GL_TRIANGLES, 0, m_particlevertices.size()); + + m_shader->unbind(); + m_vao->unbind(); + m_buffer->unbind(gl::buffer::ARRAY_BUFFER); +} + +//--------------------------------------------------------------------------- diff --git a/opengl33particles.h b/opengl33particles.h new file mode 100644 index 00000000..569c094b --- /dev/null +++ b/opengl33particles.h @@ -0,0 +1,54 @@ +/* +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/buffer.h" +#include "gl/vao.h" +#include "gl/shader.h" + +class opengl_camera; + +// particle data visualizer +class opengl33_particles { +public: +// constructors + opengl33_particles() = default; + +// methods + void + update( opengl_camera const &Camera ); + void + render( ); +private: +// types + struct particle_vertex { + glm::vec3 position; // 3d space + glm::vec4 color; // rgba, unsigned byte format + glm::vec2 texture; // uv space + }; +/* + using sourcedistance_pair = std::pair; + using source_sequence = std::vector; +*/ + using particlevertex_sequence = std::vector; +// methods +// members +/* + source_sequence m_sources; // list of particle sources visible in current render pass, with their respective distances to the camera +*/ + particlevertex_sequence m_particlevertices; // geometry data of visible particles, generated on the cpu end + std::optional m_buffer; + std::optional m_vao; + std::unique_ptr m_shader; + + std::size_t m_buffercapacity{ 0 }; // total capacity of the last established buffer +}; + +//--------------------------------------------------------------------------- diff --git a/opengl33precipitation.cpp b/opengl33precipitation.cpp new file mode 100644 index 00000000..308ccec6 --- /dev/null +++ b/opengl33precipitation.cpp @@ -0,0 +1,159 @@ +/* +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 "opengl33precipitation.h" + +#include "Globals.h" +#include "renderer.h" +#include "simulationenvironment.h" + +opengl33_precipitation::~opengl33_precipitation() { + // TODO: release allocated resources +} + +void +opengl33_precipitation::create( int const Tesselation ) { + + m_vertices.clear(); + m_uvs.clear(); + m_indices.clear(); + + auto const heightfactor { 10.f }; // height-to-radius factor + auto const verticaltexturestretchfactor { 1.5f }; // crude motion blur + + // create geometry chunk + auto const latitudes { 3 }; // just a cylinder with end cones + auto const longitudes { Tesselation }; + auto const longitudehalfstep { 0.5f * static_cast( 2.0 * M_PI * 1.f / longitudes ) }; // for crude uv correction + + std::uint16_t index = 0; + +// auto const radius { 25.f }; // cylinder radius + std::vector radii { 25.f, 10.f, 5.f, 1.f }; + for( auto radius : radii ) { + + for( int i = 0; i <= latitudes; ++i ) { + + auto const latitude{ static_cast( M_PI * ( -0.5f + (float)( i ) / latitudes ) ) }; + auto const z{ std::sin( latitude ) }; + auto const zr{ std::cos( latitude ) }; + + for( int j = 0; j <= longitudes; ++j ) { + // NOTE: for the first and last row half of the points we create end up unused but, eh + auto const longitude{ static_cast( 2.0 * M_PI * (float)( j ) / longitudes ) }; + auto const x{ std::cos( longitude ) }; + auto const y{ std::sin( longitude ) }; + // NOTE: cartesian to opengl swap would be: -x, -z, -y + m_vertices.emplace_back( glm::vec3( -x * zr, -z * heightfactor, -y * zr ) * radius ); + // uvs + // NOTE: first and last row receives modified u values to deal with limitation of mapping onto triangles + auto u = ( + i == 0 ? longitude + longitudehalfstep : + i == latitudes ? longitude - longitudehalfstep : + longitude ); + m_uvs.emplace_back( + u / ( 2.0 * M_PI ) * radius, + 1.f - (float)( i ) / latitudes * radius * heightfactor * 0.5f / verticaltexturestretchfactor ); + + if( ( i == 0 ) || ( j == 0 ) ) { + // initial edge of the dome, don't start indices yet + ++index; + } + else { + // the end cones are built from one triangle of each quad, the middle rows use both + if( i < latitudes ) { + m_indices.emplace_back( index - 1 - ( longitudes + 1 ) ); + m_indices.emplace_back( index - 1 ); + m_indices.emplace_back( index ); + } + if( i > 1 ) { + m_indices.emplace_back( index ); + m_indices.emplace_back( index - ( longitudes + 1 ) ); + m_indices.emplace_back( index - 1 - ( longitudes + 1 ) ); + } + ++index; + } + } // longitude + } // latitude + } // radius +} + +void +opengl33_precipitation::update() { + + if (!m_shader) + { + gl::shader vert("precipitation.vert"); + gl::shader frag("precipitation.frag"); + m_shader.emplace(std::vector>({vert, frag})); + } + + if (!m_vertexbuffer) { + m_vao.emplace(); + m_vao->bind(); + + if( m_vertices.empty() ) { + // create visualization mesh + create( 18 ); + } + // build the buffers + m_vertexbuffer.emplace(); + m_vertexbuffer->allocate(gl::buffer::ARRAY_BUFFER, m_vertices.size() * sizeof( glm::vec3 ), GL_STATIC_DRAW); + m_vertexbuffer->upload(gl::buffer::ARRAY_BUFFER, m_vertices.data(), 0, m_vertices.size() * sizeof( glm::vec3 )); + + m_vao->setup_attrib(*m_vertexbuffer, 0, 3, GL_FLOAT, sizeof(glm::vec3), 0); + + m_uvbuffer.emplace(); + m_uvbuffer->allocate(gl::buffer::ARRAY_BUFFER, m_uvs.size() * sizeof( glm::vec2 ), GL_STATIC_DRAW); + m_uvbuffer->upload(gl::buffer::ARRAY_BUFFER, m_uvs.data(), 0, m_uvs.size() * sizeof( glm::vec2 )); + + m_vao->setup_attrib(*m_uvbuffer, 1, 2, GL_FLOAT, sizeof(glm::vec2), 0); + + m_indexbuffer.emplace(); + m_indexbuffer->allocate(gl::buffer::ELEMENT_ARRAY_BUFFER, m_indices.size() * sizeof( unsigned short ), GL_STATIC_DRAW); + m_indexbuffer->upload(gl::buffer::ELEMENT_ARRAY_BUFFER, m_indices.data(), 0, m_indices.size() * sizeof( unsigned short )); + m_vao->setup_ebo(*m_indexbuffer); + + m_vao->unbind(); + // NOTE: vertex and index source data is superfluous past this point, but, eh + } + + // TODO: include weather type check in the entry conditions + if( m_overcast == Global.Overcast ) { return; } + + m_overcast = Global.Overcast; + + std::string const densitysuffix { ( + m_overcast < 1.35 ? + "_light" : + "_medium" ) }; + if( Global.Weather == "rain:" ) { + m_texture = GfxRenderer->Fetch_Texture( "fx/rain" + densitysuffix ); + } + else if( Global.Weather == "snow:" ) { + m_texture = GfxRenderer->Fetch_Texture( "fx/snow" + densitysuffix ); + } +} + +void +opengl33_precipitation::render() { + + if( !m_shader ) { return; } + if( !m_vao ) { return; } + + GfxRenderer->Bind_Texture( 0, m_texture ); + + m_shader->bind(); + m_vao->bind(); + + ::glDrawElements( GL_TRIANGLES, static_cast( m_indices.size() ), GL_UNSIGNED_SHORT, reinterpret_cast( 0 ) ); + + m_vao->unbind(); +} diff --git a/opengl33precipitation.h b/opengl33precipitation.h new file mode 100644 index 00000000..0487bd60 --- /dev/null +++ b/opengl33precipitation.h @@ -0,0 +1,43 @@ +/* +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 "Texture.h" +#include "gl/vao.h" +#include "gl/shader.h" + +class opengl33_precipitation { + +public: +// constructors + opengl33_precipitation() = default; +// destructor + ~opengl33_precipitation(); +// methods + void + update(); + void + render(); + +private: +// methods + void create( int const Tesselation ); +// members + std::vector m_vertices; + std::vector m_uvs; + std::vector m_indices; + texture_handle m_texture { -1 }; + float m_overcast { -1.f }; // cached overcast level, difference from current state triggers texture update + std::optional m_vertexbuffer; + std::optional m_uvbuffer; + std::optional m_indexbuffer; + std::optional m_shader; + std::optional m_vao; +}; diff --git a/opengl33renderer.cpp b/opengl33renderer.cpp new file mode 100644 index 00000000..027fb9b7 --- /dev/null +++ b/opengl33renderer.cpp @@ -0,0 +1,3904 @@ +/* +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 "opengl33renderer.h" + +#include "color.h" +#include "Globals.h" +#include "Timer.h" +#include "Train.h" +#include "Camera.h" +#include "simulation.h" +#include "Logs.h" +#include "utilities.h" +#include "simulationtime.h" +#include "application.h" +#include "AnimModel.h" + +int const EU07_PICKBUFFERSIZE{1024}; // size of (square) textures bound with the pick framebuffer + +bool opengl33_renderer::Init(GLFWwindow *Window) +{ + if (!Init_caps()) + return false; + + WriteLog("preparing renderer.."); + + m_window = Window; + + gl::glsl_common_setup(); + + if (true == Global.ScaleSpecularValues) + { + m_specularopaquescalefactor = 0.25f; + m_speculartranslucentscalefactor = 1.5f; + } + + // rgb value for 5780 kelvin + Global.DayLight.diffuse[0] = 255.0f / 255.0f; + Global.DayLight.diffuse[1] = 242.0f / 255.0f; + Global.DayLight.diffuse[2] = 231.0f / 255.0f; + Global.DayLight.is_directional = true; + m_sunlight.id = opengl33_renderer::sunlight; + + // create dynamic light pool + for (int idx = 0; idx < Global.DynamicLightCount; ++idx) + { + opengl33_light light; + light.id = 1 + idx; + + light.is_directional = false; + + m_lights.emplace_back(light); + } + // preload some common textures + WriteLog("Loading common gfx data..."); + m_glaretexture = Fetch_Texture("fx/lightglare"); + m_suntexture = Fetch_Texture("fx/sun"); + m_moontexture = Fetch_Texture("fx/moon"); + m_smoketexture = Fetch_Texture("fx/smoke"); + WriteLog("...gfx data pre-loading done"); + + // prepare basic geometry chunks + float const size = 2.5f / 2.0f; + auto const geometrybank = m_geometry.create_bank(); + m_billboardgeometry = m_geometry.create_chunk( + gfx::vertex_array{ + {{-size, size, 0.f}, glm::vec3(), {1.f, 1.f}}, {{size, size, 0.f}, glm::vec3(), {0.f, 1.f}}, {{-size, -size, 0.f}, glm::vec3(), {1.f, 0.f}}, {{size, -size, 0.f}, glm::vec3(), {0.f, 0.f}}}, + geometrybank, GL_TRIANGLE_STRIP); + m_empty_vao = std::make_unique(); + + try + { + m_vertex_shader = std::make_unique("vertex.vert"); + m_line_shader = make_shader("traction.vert", "traction.frag"); + m_freespot_shader = make_shader("freespot.vert", "freespot.frag"); + m_shadow_shader = make_shader("simpleuv.vert", "shadowmap.frag"); + m_alpha_shadow_shader = make_shader("simpleuv.vert", "alphashadowmap.frag"); + m_pick_shader = make_shader("vertexonly.vert", "pick.frag"); + m_billboard_shader = make_shader("simpleuv.vert", "billboard.frag"); + m_celestial_shader = make_shader("celestial.vert", "celestial.frag"); + if (Global.gfx_usegles) + m_depth_pointer_shader = make_shader("quad.vert", "gles_depthpointer.frag"); + m_invalid_material = Fetch_Material("invalid"); + } + catch (gl::shader_exception const &e) + { + ErrorLog("invalid shader: " + std::string(e.what())); + return false; + } + + scene_ubo = std::make_unique(sizeof(gl::scene_ubs), 0); + model_ubo = std::make_unique(sizeof(gl::model_ubs), 1, GL_STREAM_DRAW); + light_ubo = std::make_unique(sizeof(gl::light_ubs), 2); + + // better initialize with 0 to not crash driver/whole system + // when we forget + memset(&light_ubs, 0, sizeof(light_ubs)); + memset(&model_ubs, 0, sizeof(model_ubs)); + memset(&scene_ubs, 0, sizeof(scene_ubs)); + + light_ubo->update(light_ubs); + model_ubo->update(model_ubs); + scene_ubo->update(scene_ubs); + + int samples = 1 << Global.iMultisampling; + if (!Global.gfx_usegles && samples > 1) + glEnable(GL_MULTISAMPLE); + + m_pfx_motionblur = std::make_unique("motionblur"); + m_pfx_tonemapping = std::make_unique("tonemapping"); + + m_empty_cubemap = std::make_unique(); + m_empty_cubemap->alloc(Global.gfx_format_color, 16, 16, GL_RGB, GL_FLOAT); + + m_viewports.push_back(std::make_unique()); + viewport_config &default_viewport = *m_viewports.front().get(); + default_viewport.width = Global.gfx_framebuffer_width; + default_viewport.height = Global.gfx_framebuffer_height; + default_viewport.main = true; + default_viewport.window = m_window; + default_viewport.draw_range = 1.0f; + + if (!init_viewport(default_viewport)) + return false; + glfwMakeContextCurrent(m_window); + gl::buffer::unbind(); + + if (Global.gfx_shadowmap_enabled) + { + m_shadow_fb = std::make_unique(); + m_shadow_tex = std::make_unique(); + m_shadow_tex->alloc_rendertarget(Global.gfx_format_depth, GL_DEPTH_COMPONENT, m_shadowbuffersize, m_shadowbuffersize); + m_shadow_fb->attach(*m_shadow_tex, GL_DEPTH_ATTACHMENT); + + if (!m_shadow_fb->is_complete()) + return false; + + WriteLog("shadows enabled"); + + m_cabshadows_fb = std::make_unique(); + m_cabshadows_tex = std::make_unique(); + m_cabshadows_tex->alloc_rendertarget(Global.gfx_format_depth, GL_DEPTH_COMPONENT, m_shadowbuffersize, m_shadowbuffersize); + m_cabshadows_fb->attach(*m_cabshadows_tex, GL_DEPTH_ATTACHMENT); + + if (!m_cabshadows_fb->is_complete()) + return false; + + WriteLog("cabshadows enabled"); + } + + if (Global.gfx_envmap_enabled) + { + m_env_rb = std::make_unique(); + m_env_rb->alloc(Global.gfx_format_depth, gl::ENVMAP_SIZE, gl::ENVMAP_SIZE); + m_env_tex = std::make_unique(); + m_env_tex->alloc(Global.gfx_format_color, gl::ENVMAP_SIZE, gl::ENVMAP_SIZE, GL_RGB, GL_FLOAT); + + m_env_fb = std::make_unique(); + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + for (int i = 0; i < 6; i++) + { + m_env_fb->attach(*m_empty_cubemap, i, GL_COLOR_ATTACHMENT0); + m_env_fb->clear(GL_COLOR_BUFFER_BIT); + } + + m_env_fb->detach(GL_COLOR_ATTACHMENT0); + m_env_fb->attach(*m_env_rb, GL_DEPTH_ATTACHMENT); + + WriteLog("envmap enabled"); + } + + m_pick_tex = std::make_unique(); + m_pick_tex->alloc_rendertarget(GL_RGB8, GL_RGB, EU07_PICKBUFFERSIZE, EU07_PICKBUFFERSIZE); + m_pick_rb = std::make_unique(); + m_pick_rb->alloc(Global.gfx_format_depth, EU07_PICKBUFFERSIZE, EU07_PICKBUFFERSIZE); + m_pick_fb = std::make_unique(); + m_pick_fb->attach(*m_pick_tex, GL_COLOR_ATTACHMENT0); + m_pick_fb->attach(*m_pick_rb, GL_DEPTH_ATTACHMENT); + + if (!m_pick_fb->is_complete()) + return false; + + m_picking_pbo = std::make_unique(); + m_picking_node_pbo = std::make_unique(); + + m_depth_pointer_pbo = std::make_unique(); + if (!Global.gfx_usegles && Global.iMultisampling) + { + m_depth_pointer_rb = std::make_unique(); + m_depth_pointer_rb->alloc(Global.gfx_format_depth, 1, 1); + + m_depth_pointer_fb = std::make_unique(); + m_depth_pointer_fb->attach(*m_depth_pointer_rb, GL_DEPTH_ATTACHMENT); + + if (!m_depth_pointer_fb->is_complete()) + return false; + } + else if (Global.gfx_usegles) + { + m_depth_pointer_tex = std::make_unique(); + + GLenum format = Global.gfx_format_depth; + if (Global.gfx_skippipeline) + { + gl::framebuffer::unbind(); + GLint bits, type; + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH, GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, &bits); + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH, GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE, &type); + if (type == GL_FLOAT && bits == 32) + format = GL_DEPTH_COMPONENT32F; + else if (bits == 16) + format = GL_DEPTH_COMPONENT16; + else if (bits == 24) + format = GL_DEPTH_COMPONENT24; + else if (bits == 32) + format = GL_DEPTH_COMPONENT32; + } + + m_depth_pointer_tex->alloc_rendertarget(format, GL_DEPTH_COMPONENT, Global.gfx_framebuffer_width, Global.gfx_framebuffer_height); + + m_depth_pointer_rb = std::make_unique(); + m_depth_pointer_rb->alloc(GL_R16UI, Global.gfx_framebuffer_width, Global.gfx_framebuffer_height); + + m_depth_pointer_fb = std::make_unique(); + m_depth_pointer_fb->attach(*m_depth_pointer_tex, GL_DEPTH_ATTACHMENT); + + m_depth_pointer_fb2 = std::make_unique(); + m_depth_pointer_fb2->attach(*m_depth_pointer_rb, GL_COLOR_ATTACHMENT0); + + if (!m_depth_pointer_fb->is_complete()) + return false; + + if (!m_depth_pointer_fb2->is_complete()) + return false; + } + + m_timequery.emplace(gl::query::TIME_ELAPSED); + m_timequery->begin(); + + WriteLog("picking objects created"); + + WriteLog("renderer initialization finished!"); + + return true; +} +/* +bool opengl33_renderer::AddViewport(const global_settings::extraviewport_config &conf) +{ + m_viewports.push_back(std::make_unique()); + viewport_config &vp = *m_viewports.back().get(); + vp.width = conf.width; + vp.height = conf.height; + vp.window = Application.window(-1, true, vp.width, vp.height, Application.find_monitor(conf.monitor)); + vp.camera_transform = conf.transform; + vp.draw_range = conf.draw_range; + + bool ret = init_viewport(vp); + glfwMakeContextCurrent(m_window); + gl::buffer::unbind(); + + return ret; +} +*/ +bool opengl33_renderer::init_viewport(viewport_config &vp) +{ + glfwMakeContextCurrent(vp.window); + + WriteLog("init viewport: " + std::to_string(vp.width) + ", " + std::to_string(vp.height)); + + glfwSwapInterval( Global.VSync ? 1 : 0 ); + + 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 + + glFrontFace(GL_CCW); + glEnable(GL_CULL_FACE); + + glEnable(GL_DEPTH_TEST); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + + if (!Global.gfx_usegles) + glClearDepth(0.0f); + else + glClearDepthf(0.0f); + + glDepthFunc(GL_GEQUAL); + + if (GLAD_GL_ARB_clip_control) + glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); + else if (GLAD_GL_EXT_clip_control) + glClipControlEXT(GL_LOWER_LEFT_EXT, GL_ZERO_TO_ONE_EXT); + + if (!Global.gfx_usegles) + glEnable(GL_PROGRAM_POINT_SIZE); + + if (!gl::vao::use_vao) + { + GLuint v; + glGenVertexArrays(1, &v); + glBindVertexArray(v); + } + + int samples = 1 << Global.iMultisampling; + + model_ubo->bind_uniform(); + scene_ubo->bind_uniform(); + light_ubo->bind_uniform(); + + if (!Global.gfx_skippipeline) + { + vp.msaa_rbc = std::make_unique(); + vp.msaa_rbc->alloc(Global.gfx_format_color, vp.width, vp.height, samples); + + vp.msaa_rbd = std::make_unique(); + vp.msaa_rbd->alloc(Global.gfx_format_depth, vp.width, vp.height, samples); + + vp.msaa_fb = std::make_unique(); + vp.msaa_fb->attach(*vp.msaa_rbc, GL_COLOR_ATTACHMENT0); + vp.msaa_fb->attach(*vp.msaa_rbd, GL_DEPTH_ATTACHMENT); + + if (Global.gfx_postfx_motionblur_enabled) + { + vp.msaa_rbv = std::make_unique(); + vp.msaa_rbv->alloc(Global.gfx_postfx_motionblur_format, vp.width, vp.height, samples); + vp.msaa_fb->attach(*vp.msaa_rbv, GL_COLOR_ATTACHMENT1); + + vp.main_tex = std::make_unique(); + vp.main_tex->alloc_rendertarget(Global.gfx_format_color, GL_RGB, vp.width, vp.height, 1, GL_CLAMP_TO_EDGE); + + vp.main_fb = std::make_unique(); + vp.main_fb->attach(*vp.main_tex, GL_COLOR_ATTACHMENT0); + + vp.main_texv = std::make_unique(); + vp.main_texv->alloc_rendertarget(Global.gfx_postfx_motionblur_format, GL_RG, vp.width, vp.height); + vp.main_fb->attach(*vp.main_texv, GL_COLOR_ATTACHMENT1); + vp.main_fb->setup_drawing(2); + + if (!vp.main_fb->is_complete()) + return false; + + WriteLog("motion blur enabled"); + } + + if (!vp.msaa_fb->is_complete()) + return false; + + vp.main2_tex = std::make_unique(); + vp.main2_tex->alloc_rendertarget(Global.gfx_format_color, GL_RGB, vp.width, vp.height); + + vp.main2_fb = std::make_unique(); + vp.main2_fb->attach(*vp.main2_tex, GL_COLOR_ATTACHMENT0); + if (!vp.main2_fb->is_complete()) + return false; + } + + return true; +} + +std::unique_ptr opengl33_renderer::make_shader(std::string v, std::string f) +{ + gl::shader vert(v); + gl::shader frag(f); + gl::program *prog = new gl::program({vert, frag}); + return std::unique_ptr(prog); +} + +bool opengl33_renderer::Render() +{ + Timer::subsystem.gfx_total.start(); + + if (!Global.gfx_usegles) + { + std::optional result = m_timequery->result(); + if (result) { + m_gllasttime = *result; + m_timequery->begin(); + } + } + + // fetch simulation data + if (simulation::is_ready) + { + m_sunlight = Global.DayLight; + // quantize sun angle to reduce shadow crawl + auto const quantizationstep{0.004f}; + m_sunlight.direction = glm::normalize(quantizationstep * glm::roundEven(m_sunlight.direction * (1.f / quantizationstep))); + } + // generate new frame + m_renderpass.draw_mode = rendermode::none; // force setup anew + m_debugstats = debug_stats(); + + for (auto &viewport : m_viewports) { + Render_pass(*viewport, rendermode::color); + } + + glfwMakeContextCurrent(m_window); + gl::buffer::unbind(); + m_current_viewport = &(*m_viewports.front()); + + m_drawcount = m_cellqueue.size(); + m_debugtimestext.clear(); + m_debugtimestext += "cpu: " + to_string(Timer::subsystem.gfx_color.average(), 2) + " ms (" + std::to_string(m_cellqueue.size()) + " sectors)\n" += + "cpu swap: " + to_string(Timer::subsystem.gfx_swap.average(), 2) + " ms\n" += "uilayer: " + to_string(Timer::subsystem.gfx_gui.average(), 2) + "ms\n" += + "mainloop total: " + to_string(Timer::subsystem.mainloop_total.average(), 2) + "ms\n"; + + if (!Global.gfx_usegles) + { + m_timequery->end(); + + if (m_gllasttime) + m_debugtimestext += "gpu: " + to_string((double)(m_gllasttime / 1000ULL) / 1000.0, 3) + "ms"; + } + + m_debugstatstext = "drawcalls: " + to_string(m_debugstats.drawcalls) + "\n" + " vehicles: " + to_string(m_debugstats.dynamics) + "\n" + " models: " + to_string(m_debugstats.models) + "\n" + + " submodels: " + to_string(m_debugstats.submodels) + "\n" + " paths: " + to_string(m_debugstats.paths) + "\n" + " shapes: " + to_string(m_debugstats.shapes) + "\n" + + " traction: " + to_string(m_debugstats.traction) + "\n" + " lines: " + to_string(m_debugstats.lines); + + if (DebugModeFlag) + m_debugtimestext += m_textures.info(); + + ++m_framestamp; + + Timer::subsystem.gfx_total.stop(); + + return true; // for now always succeed +} + +void opengl33_renderer::SwapBuffers() +{ + Timer::subsystem.gfx_swap.start(); + + for (auto &viewport : m_viewports) { + if (viewport->window) + glfwSwapBuffers(viewport->window); + } + + // swapbuffers() could unbind current buffers so we prepare for it on our end + gfx::opengl_vbogeometrybank::reset(); + Timer::subsystem.gfx_swap.stop(); +} + +void opengl33_renderer::draw_debug_ui() +{ + if (!debug_ui_active) + return; + + if (ImGui::Begin("headlight config", &debug_ui_active)) + { + ImGui::SetWindowSize(ImVec2(0, 0)); + + headlight_config_s &conf = headlight_config; + ImGui::SliderFloat("in_cutoff", &conf.in_cutoff, 0.9f, 1.1f); + ImGui::SliderFloat("out_cutoff", &conf.out_cutoff, 0.9f, 1.1f); + + ImGui::SliderFloat("falloff_linear", &conf.falloff_linear, 0.0f, 1.0f, "%.3f", 2.0f); + ImGui::SliderFloat("falloff_quadratic", &conf.falloff_quadratic, 0.0f, 1.0f, "%.3f", 2.0f); + + ImGui::SliderFloat("ambient", &conf.ambient, 0.0f, 3.0f); + ImGui::SliderFloat("intensity", &conf.intensity, 0.0f, 10.0f); + } + ImGui::End(); +} + +// runs jobs needed to generate graphics for specified render pass +void opengl33_renderer::Render_pass(viewport_config &vp, rendermode const Mode) +{ + setup_pass(vp, m_renderpass, Mode); + switch (m_renderpass.draw_mode) + { + + case rendermode::color: + { + glDebug("rendermode::color"); + + glDebug("context switch"); + glfwMakeContextCurrent(vp.window); + gl::buffer::unbind(); + m_current_viewport = &vp; + + if (!simulation::is_ready) + { + gl::framebuffer::unbind(); + glClearColor(0.0f, 0.5f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + if (vp.main) + Application.render_ui(); + break; + } + + scene_ubs.time = Timer::GetTime(); + scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION); + scene_ubo->update(scene_ubs); + scene_ubo->bind_uniform(); + + m_colorpass = m_renderpass; + + if (m_widelines_supported) + glLineWidth(1.0f); + + if (!Global.gfx_usegles) + { + if (Global.bWireFrame) + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + else + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + + setup_shadow_map(nullptr, m_renderpass); + setup_env_map(nullptr); + + if (Global.gfx_shadowmap_enabled && vp.main) + { + glDebug("render shadowmap start"); + Timer::subsystem.gfx_shadows.start(); + + Render_pass(vp, rendermode::shadows); + if (!FreeFlyModeFlag && Global.render_cab) + Render_pass(vp, rendermode::cabshadows); + setup_pass(vp, m_renderpass, Mode); // restore draw mode. TBD, TODO: render mode stack + + Timer::subsystem.gfx_shadows.stop(); + glDebug("render shadowmap end"); + } + + if (Global.gfx_envmap_enabled && vp.main) + { + // potentially update environmental cube map + if (Render_reflections(vp)) + setup_pass(vp, m_renderpass, Mode); // restore color pass settings + setup_env_map(m_env_tex.get()); + } + + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + + setup_drawing(false); + + glm::ivec2 target_size(vp.width, vp.height); + if (vp.main) // TODO: update window sizes also for extra viewports + target_size = glm::ivec2(Global.iWindowWidth, Global.iWindowHeight); + + if (!Global.gfx_skippipeline) + { + vp.msaa_fb->bind(); + + if (Global.gfx_postfx_motionblur_enabled) + vp.msaa_fb->setup_drawing(2); + else + vp.msaa_fb->setup_drawing(1); + + glViewport(0, 0, vp.width, vp.height); + + vp.msaa_fb->clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + else + { + if (!Global.gfx_usegles && !Global.gfx_shadergamma) + glEnable(GL_FRAMEBUFFER_SRGB); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(0, 0, target_size.x, target_size.y); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + glEnable(GL_DEPTH_TEST); + + Timer::subsystem.gfx_color.start(); + setup_matrices(); + setup_drawing(true); + + glm::mat4 future; + if (Global.pCamera.m_owner != nullptr) + { + auto const *vehicle = Global.pCamera.m_owner; + glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW); + future = glm::translate(mv, -glm::vec3(vehicle->get_future_movement())) * glm::inverse(mv); + } + + model_ubs.future = glm::mat4(); + + glDebug("render environment"); + + scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION); + scene_ubo->update(scene_ubs); + Render(&simulation::Environment); + + // opaque parts... + setup_drawing(false); + + // without rain/snow we can render the cab early to limit the overdraw + // precipitation happens when overcast is in 1-2 range + if (!FreeFlyModeFlag && Global.Overcast <= 1.0f && Global.render_cab) + { + glDebug("render cab opaque"); + if (Global.gfx_shadowmap_enabled) + setup_shadow_map(m_cabshadows_tex.get(), m_cabshadowpass); + + auto const *vehicle = simulation::Train->Dynamic(); + Render_cab(vehicle, vehicle->InteriorLightLevel, false); + } + + glDebug("render opaque region"); + + model_ubs.future = future; + + if (Global.gfx_shadowmap_enabled) + setup_shadow_map(m_shadow_tex.get(), m_shadowpass); + + Render(simulation::Region); + + // ...translucent parts + glDebug("render translucent region"); + setup_drawing(true); + Render_Alpha(simulation::Region); + + // particles + Render_particles(); + + // precipitation; done at end, only before cab render + Render_precipitation(); + + // cab render + if (false == FreeFlyModeFlag && Global.render_cab) + { + glDebug("render translucent cab"); + model_ubs.future = glm::mat4(); + if (Global.gfx_shadowmap_enabled) + setup_shadow_map(m_cabshadows_tex.get(), m_cabshadowpass); + // cache shadow colour in case we need to account for cab light + auto const *vehicle{simulation::Train->Dynamic()}; + if (Global.Overcast > 1.0f) + { + // with active precipitation draw the opaque cab parts here to mask rain/snow placed 'inside' the cab + setup_drawing(false); + Render_cab(vehicle, vehicle->InteriorLightLevel, false); + setup_drawing(true); + } + Render_cab(vehicle, vehicle->InteriorLightLevel, true); + } + + Timer::subsystem.gfx_color.stop(); + + setup_shadow_map(nullptr, m_renderpass); + setup_env_map(nullptr); + + if (!Global.gfx_usegles) + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + if (!Global.gfx_skippipeline) + { + if (Global.gfx_postfx_motionblur_enabled) + { + vp.main_fb->clear(GL_COLOR_BUFFER_BIT); + vp.msaa_fb->blit_to(vp.main_fb.get(), vp.width, vp.height, GL_COLOR_BUFFER_BIT, GL_COLOR_ATTACHMENT0); + vp.msaa_fb->blit_to(vp.main_fb.get(), vp.width, vp.height, GL_COLOR_BUFFER_BIT, GL_COLOR_ATTACHMENT1); + + model_ubs.param[0].x = m_framerate / (1.0 / Global.gfx_postfx_motionblur_shutter); + model_ubo->update(model_ubs); + m_pfx_motionblur->apply({vp.main_tex.get(), vp.main_texv.get()}, vp.main2_fb.get()); + } + else + { + vp.main2_fb->clear(GL_COLOR_BUFFER_BIT); + vp.msaa_fb->blit_to(vp.main2_fb.get(), vp.width, vp.height, GL_COLOR_BUFFER_BIT, GL_COLOR_ATTACHMENT0); + } + + if (!Global.gfx_usegles && !Global.gfx_shadergamma) + glEnable(GL_FRAMEBUFFER_SRGB); + + glViewport(0, 0, target_size.x, target_size.y); + m_pfx_tonemapping->apply(*vp.main2_tex, nullptr); + opengl_texture::reset_unit_cache(); + } + + if (!Global.gfx_usegles && !Global.gfx_shadergamma) + glDisable(GL_FRAMEBUFFER_SRGB); + + glDebug("uilayer render"); + Timer::subsystem.gfx_gui.start(); + + if (vp.main) { + draw_debug_ui(); + Application.render_ui(); + } + + Timer::subsystem.gfx_gui.stop(); + + // restore binding + scene_ubo->bind_uniform(); + + glDebug("rendermode::color end"); + break; + } + + case rendermode::shadows: + { + if (!simulation::is_ready) + break; + + glDebug("rendermode::shadows"); + + glEnable(GL_DEPTH_TEST); + + glViewport(0, 0, m_shadowbuffersize, m_shadowbuffersize); + m_shadow_fb->bind(); + m_shadow_fb->clear(GL_DEPTH_BUFFER_BIT); + + setup_matrices(); + setup_drawing(false); + + scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION); + scene_ubo->update(scene_ubs); + Render(simulation::Region); + + m_shadowpass = m_renderpass; + + m_shadow_fb->unbind(); + + glDebug("rendermodeshadows ::end"); + + break; + } + + case rendermode::cabshadows: + { + glDebug("rendermode::cabshadows"); + + glEnable(GL_DEPTH_TEST); + + glViewport(0, 0, m_shadowbuffersize, m_shadowbuffersize); + m_cabshadows_fb->bind(); + m_cabshadows_fb->clear(GL_DEPTH_BUFFER_BIT); + + setup_matrices(); + setup_drawing(false); + + scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION); + scene_ubo->update(scene_ubs); + + Render_cab(simulation::Train->Dynamic(), 0.0f, false); + Render_cab(simulation::Train->Dynamic(), 0.0f, true); + m_cabshadowpass = m_renderpass; + + m_cabshadows_fb->unbind(); + + glDebug("rendermode::cabshadows end"); + + break; + } + + case rendermode::reflections: + { + if (!simulation::is_ready) + break; + + glDebug("rendermode::reflections"); + + // NOTE: buffer attachment and viewport setup in this mode is handled by the wrapper method + glEnable(GL_DEPTH_TEST); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + m_env_fb->clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + m_env_fb->bind(); + + setup_env_map(m_empty_cubemap.get()); + + setup_matrices(); + setup_shadow_map(nullptr, m_renderpass); + + // render + setup_drawing(true); + + scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION); + scene_ubo->update(scene_ubs); + Render(&simulation::Environment); + + // opaque parts... + setup_drawing(false); + setup_shadow_map(m_shadow_tex.get(), m_shadowpass); + + scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION); + scene_ubo->update(scene_ubs); + Render(simulation::Region); + + m_env_fb->unbind(); + + glDebug("rendermode::reflections end"); + + break; + } + + case rendermode::pickcontrols: + { + if (!simulation::is_ready || !simulation::Train) + break; + + glDebug("rendermode::pickcontrols"); + + glEnable(GL_DEPTH_TEST); + glViewport(0, 0, EU07_PICKBUFFERSIZE, EU07_PICKBUFFERSIZE); + m_pick_fb->bind(); + m_pick_fb->clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + m_pickcontrolsitems.clear(); + setup_matrices(); + setup_drawing(false); + + scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION); + scene_ubo->update(scene_ubs); + if (simulation::Train != nullptr) { + Render_cab(simulation::Train->Dynamic(), 0.0f); + Render(simulation::Train->Dynamic()); + } + + m_pick_fb->unbind(); + + glDebug("rendermode::pickcontrols end"); + break; + } + + case rendermode::pickscenery: + { + if (!simulation::is_ready) + break; + + glEnable(GL_DEPTH_TEST); + glViewport(0, 0, EU07_PICKBUFFERSIZE, EU07_PICKBUFFERSIZE); + m_pick_fb->bind(); + m_pick_fb->clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + m_picksceneryitems.clear(); + setup_matrices(); + setup_drawing(false); + + scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION); + scene_ubo->update(scene_ubs); + Render(simulation::Region); + + break; + } + + default: + { + break; + } + } +} + +// creates dynamic environment cubemap +bool opengl33_renderer::Render_reflections(viewport_config &vp) +{ + if (Global.ReflectionUpdateInterval == 0.0) + return false; + + auto const timestamp = Timer::GetTime(); + + if ((timestamp - m_environmentupdatetime < Global.ReflectionUpdateInterval) + && (glm::length(m_renderpass.pass_camera.position() - m_environmentupdatelocation) < 1000.0)) + { + // run update every 5+ mins of simulation time, or at least 1km from the last location + return false; + } + m_environmentupdatetime = timestamp; + m_environmentupdatelocation = m_renderpass.pass_camera.position(); + + glViewport(0, 0, gl::ENVMAP_SIZE, gl::ENVMAP_SIZE); + for (m_environmentcubetextureface = 0; m_environmentcubetextureface < 6; ++m_environmentcubetextureface) + { + m_env_fb->attach(*m_env_tex, m_environmentcubetextureface, GL_COLOR_ATTACHMENT0); + if (m_env_fb->is_complete()) + Render_pass(vp, rendermode::reflections); + } + m_env_tex->generate_mipmaps(); + m_env_fb->detach(GL_COLOR_ATTACHMENT0); + + return true; +} + +glm::mat4 opengl33_renderer::perspective_projection(float fovy, float aspect, float znear, float zfar) +{ + if (GLAD_GL_ARB_clip_control || GLAD_GL_EXT_clip_control) + { + const float f = 1.0f / tan(fovy / 2.0f); + + // when clip_control available, use projection matrix with 1..0 Z range and infinite zfar + return glm::mat4( // + f / aspect, 0.0f, 0.0f, 0.0f, // + 0.0f, f, 0.0f, 0.0f, // + 0.0f, 0.0f, 0.0f, -1.0f, // + 0.0f, 0.0f, znear, 0.0f // + ); + } + else + // or use standard matrix but with 1..-1 Z range + // (reverse Z don't give any extra precision without clip_control, but it is used anyway for consistency) + return glm::mat4( // + 1.0f, 0.0f, 0.0f, 0.0f, // + 0.0f, 1.0f, 0.0f, 0.0f, // + 0.0f, 0.0f, -1.0f, 0.0f, // + 0.0f, 0.0f, 0.0f, 1.0f // + ) * + glm::perspective(fovy, aspect, znear, zfar); +} + +glm::mat4 opengl33_renderer::perpsective_frustumtest_projection(float fovy, float aspect, float znear, float zfar) +{ + // for frustum calculation, use standard opengl matrix + return glm::perspective(fovy, aspect, znear, zfar); +} + +glm::mat4 opengl33_renderer::ortho_projection(float l, float r, float b, float t, float znear, float zfar) +{ + glm::mat4 proj = glm::ortho(l, r, b, t, znear, zfar); + if (GLAD_GL_ARB_clip_control || GLAD_GL_EXT_clip_control) + // when clip_control available, use projection matrix with 1..0 Z range + return glm::mat4( // + 1.0f, 0.0f, 0.0f, 0.0f, // + 0.0f, 1.0f, 0.0f, 0.0f, // + 0.0f, 0.0f, -0.5f, 0.0f, // + 0.0f, 0.0f, 0.5f, 1.0f // + ) * + proj; + else + // or use standard matrix but with 1..-1 Z range + // (reverse Z don't give any extra precision without clip_control, but it is used anyway for consistency) + return glm::mat4( // + 1.0f, 0.0f, 0.0f, 0.0f, // + 0.0f, 1.0f, 0.0f, 0.0f, // + 0.0f, 0.0f, -1.0f, 0.0f, // + 0.0f, 0.0f, 0.0f, 1.0f // + ) * + proj; +} + +glm::mat4 opengl33_renderer::ortho_frustumtest_projection(float l, float r, float b, float t, float znear, float zfar) +{ + // for frustum calculation, use standard opengl matrix + return glm::ortho(l, r, b, t, znear, zfar); +} + +void opengl33_renderer::setup_pass(viewport_config &Viewport, renderpass_config &Config, rendermode const Mode, + float const Znear, float const Zfar, bool const Ignoredebug) +{ + + 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; + } + } + + Config.draw_range *= Viewport.draw_range; + + // setup camera + auto &camera = Config.pass_camera; + + glm::dmat4 viewmatrix(1.0); + + glm::mat4 frustumtest_proj; + + glm::ivec2 target_size(Viewport.width, Viewport.height); + if (Viewport.main) // TODO: update window sizes also for extra viewports + target_size = glm::ivec2(Global.iWindowWidth, Global.iWindowHeight); + + float const fovy = glm::radians(Global.FieldOfView / Global.ZoomFactor); + float const aspect = (float)target_size.x / std::max(1.f, (float)target_size.y); + + Config.viewport_camera.position() = Global.pCamera.Pos; + + switch (Mode) + { + case rendermode::color: + { + viewmatrix = glm::dmat4(Viewport.camera_transform); + + // modelview + if ((false == DebugCameraFlag) || (true == Ignoredebug)) + { + camera.position() = Global.pCamera.Pos; + Global.pCamera.SetMatrix(viewmatrix); + } + else + { + camera.position() = Global.pDebugCamera.Pos; + Global.pDebugCamera.SetMatrix(viewmatrix); + } + + // projection + auto const zfar = Config.draw_range * Global.fDistanceFactor * Zfar; + auto const znear = (Znear > 0.f ? Znear * zfar : 0.1f * Global.ZoomFactor); + + camera.projection() = perspective_projection(fovy, aspect, znear, zfar); + frustumtest_proj = perpsective_frustumtest_projection(fovy, aspect, znear, zfar); + break; + } + case rendermode::shadows: + { + // calculate lightview boundaries based on relevant area of the world camera frustum: + // ...setup chunk of frustum we're interested in... + auto const zfar = std::min(1.f, Global.shadowtune.depth / (Global.BaseDrawRange * Global.fDistanceFactor) * std::max(1.f, Global.ZoomFactor * 0.5f)); + renderpass_config worldview; + setup_pass(Viewport, worldview, rendermode::color, 0.f, zfar, true); + auto &frustumchunkshapepoints = worldview.pass_camera.frustum_points(); + // ...modelview matrix: determine the centre of frustum chunk in world space... + glm::vec3 frustumchunkmin, frustumchunkmax; + bounding_box(frustumchunkmin, frustumchunkmax, std::begin(frustumchunkshapepoints), std::end(frustumchunkshapepoints)); + auto const frustumchunkcentre = (frustumchunkmin + frustumchunkmax) * 0.5f; + // ...cap the vertical angle to keep shadows from getting too long... + auto const lightvector = glm::normalize(glm::vec3{m_sunlight.direction.x, std::min(m_sunlight.direction.y, -0.2f), m_sunlight.direction.z}); + // ...place the light source at the calculated centre and setup world space light view matrix... + camera.position() = worldview.pass_camera.position() + glm::dvec3{frustumchunkcentre}; + viewmatrix *= glm::lookAt(camera.position(), camera.position() + glm::dvec3{lightvector}, glm::dvec3{0.f, 1.f, 0.f}); + // ...projection matrix: calculate boundaries of the frustum chunk in light space... + auto const lightviewmatrix = glm::translate(glm::mat4{glm::mat3{viewmatrix}}, -frustumchunkcentre); + for (auto &point : frustumchunkshapepoints) + { + point = lightviewmatrix * point; + } + bounding_box(frustumchunkmin, frustumchunkmax, std::begin(frustumchunkshapepoints), std::end(frustumchunkshapepoints)); + // quantize the frustum points and add some padding, to reduce shadow shimmer on scale changes + auto const quantizationstep{std::min(Global.shadowtune.depth, 50.f)}; + frustumchunkmin = quantizationstep * glm::floor(frustumchunkmin * (1.f / quantizationstep)); + frustumchunkmax = quantizationstep * glm::ceil(frustumchunkmax * (1.f / quantizationstep)); + // ...use the dimensions to set up light projection boundaries... + // NOTE: since we only have one cascade map stage, we extend the chunk forward/back to catch areas normally covered by other stages + camera.projection() = ortho_projection(frustumchunkmin.x, frustumchunkmax.x, frustumchunkmin.y, frustumchunkmax.y, frustumchunkmin.z - 500.f, frustumchunkmax.z + 500.f); + frustumtest_proj = ortho_frustumtest_projection(frustumchunkmin.x, frustumchunkmax.x, frustumchunkmin.y, frustumchunkmax.y, frustumchunkmin.z - 500.f, frustumchunkmax.z + 500.f); + /* + // fixed ortho projection from old build, for quick quality comparisons + camera.projection() *= + ortho_projection( + -Global.shadowtune.width, Global.shadowtune.width, + -Global.shadowtune.width, Global.shadowtune.width, + -Global.shadowtune.depth, Global.shadowtune.depth ); + camera.position() = Global.pCamera.Pos - glm::dvec3{ m_sunlight.direction }; + if( camera.position().y - Global.pCamera.Pos.y < 0.1 ) { + camera.position().y = Global.pCamera.Pos.y + 0.1; + } + viewmatrix *= glm::lookAt( + camera.position(), + glm::dvec3{ Global.pCamera.Pos }, + glm::dvec3{ 0.f, 1.f, 0.f } ); + */ + // ... and adjust the projection to sample complete shadow map texels: + // get coordinates for a sample texel... + auto shadowmaptexel = glm::vec2{camera.projection() * glm::mat4{viewmatrix} * glm::vec4{0.f, 0.f, 0.f, 1.f}}; + // ...convert result from clip space to texture coordinates, and calculate adjustment... + shadowmaptexel *= m_shadowbuffersize * 0.5f; + auto shadowmapadjustment = glm::round(shadowmaptexel) - shadowmaptexel; + // ...transform coordinate change back to homogenous light space... + shadowmapadjustment /= m_shadowbuffersize * 0.5f; + // ... and bake the adjustment into the projection matrix + camera.projection() = glm::translate(glm::mat4{1.f}, glm::vec3{shadowmapadjustment, 0.f}) * camera.projection(); + + break; + } + case rendermode::cabshadows: + { + // fixed size cube large enough to enclose a vehicle compartment + // modelview + auto const lightvector = glm::normalize(glm::vec3{m_sunlight.direction.x, std::min(m_sunlight.direction.y, -0.2f), m_sunlight.direction.z}); + camera.position() = Global.pCamera.Pos - glm::dvec3{lightvector}; + viewmatrix *= glm::lookAt(camera.position(), glm::dvec3{Global.pCamera.Pos}, glm::dvec3{0.f, 1.f, 0.f}); + // projection + auto const maphalfsize{Config.draw_range * 0.5f}; + camera.projection() = ortho_projection(-maphalfsize, maphalfsize, -maphalfsize, maphalfsize, -Config.draw_range, Config.draw_range); + frustumtest_proj = ortho_frustumtest_projection(-maphalfsize, maphalfsize, -maphalfsize, maphalfsize, -Config.draw_range, Config.draw_range); + + /* + // adjust the projection to sample complete shadow map texels + auto shadowmaptexel = glm::vec2 { camera.projection() * glm::mat4{ viewmatrix } * glm::vec4{ 0.f, 0.f, 0.f, 1.f } }; + shadowmaptexel *= ( m_shadowbuffersize / 2 ) * 0.5f; + auto shadowmapadjustment = glm::round( shadowmaptexel ) - shadowmaptexel; + shadowmapadjustment /= ( m_shadowbuffersize / 2 ) * 0.5f; + camera.projection() = glm::translate( glm::mat4{ 1.f }, glm::vec3{ shadowmapadjustment, 0.f } ) * camera.projection(); + */ + break; + } + case rendermode::pickcontrols: + case rendermode::pickscenery: + { + viewmatrix = glm::dmat4(Viewport.camera_transform); + + // modelview + camera.position() = Global.pCamera.Pos; + Global.pCamera.SetMatrix(viewmatrix); + + // projection + float znear = 0.1f * Global.ZoomFactor; + float zfar = Config.draw_range * Global.fDistanceFactor; + camera.projection() = perspective_projection(fovy, aspect, znear, zfar); + frustumtest_proj = perpsective_frustumtest_projection(fovy, aspect, znear, zfar); + break; + } + case rendermode::reflections: + { + // modelview + camera.position() = (((true == DebugCameraFlag) && (false == Ignoredebug)) ? Global.pDebugCamera.Pos : Global.pCamera.Pos); + glm::dvec3 const cubefacetargetvectors[6] = {{1.0, 0.0, 0.0}, {-1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, -1.0, 0.0}, {0.0, 0.0, 1.0}, {0.0, 0.0, -1.0}}; + glm::dvec3 const cubefaceupvectors[6] = {{0.0, -1.0, 0.0}, {0.0, -1.0, 0.0}, {0.0, 0.0, 1.0}, {0.0, 0.0, -1.0}, {0.0, -1.0, 0.0}, {0.0, -1.0, 0.0}}; + auto const cubefaceindex = m_environmentcubetextureface; + viewmatrix *= glm::lookAt(camera.position(), camera.position() + cubefacetargetvectors[cubefaceindex], cubefaceupvectors[cubefaceindex]); + // projection + float znear = 0.1f * Global.ZoomFactor; + float zfar = Config.draw_range * Global.fDistanceFactor; + camera.projection() = perspective_projection(glm::radians(90.f), 1.f, znear, zfar); + frustumtest_proj = perpsective_frustumtest_projection(glm::radians(90.f), 1.f, znear, zfar); + break; + } + default: + { + break; + } + } + camera.modelview() = viewmatrix; + camera.update_frustum(frustumtest_proj); +} + +void opengl33_renderer::setup_matrices() +{ + ::glMatrixMode(GL_PROJECTION); + OpenGLMatrices.load_matrix(m_renderpass.pass_camera.projection()); + + // trim modelview matrix just to rotation, since rendering is done in camera-centric world space + ::glMatrixMode(GL_MODELVIEW); + OpenGLMatrices.load_matrix(glm::mat4(glm::mat3(m_renderpass.pass_camera.modelview()))); +} + +void opengl33_renderer::setup_drawing(bool const Alpha) +{ + if (Alpha) + { + glEnable(GL_BLEND); + glDepthMask(GL_FALSE); + m_blendingenabled = true; + } + else + { + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + m_blendingenabled = false; + } + + switch (m_renderpass.draw_mode) + { + case rendermode::color: + case rendermode::reflections: + { + glCullFace(GL_BACK); + break; + } + case rendermode::shadows: + case rendermode::cabshadows: + { + glCullFace(GL_FRONT); + break; + } + case rendermode::pickcontrols: + case rendermode::pickscenery: + { + glCullFace(GL_BACK); + break; + } + default: + { + break; + } + } +} + +// configures shadow texture unit for specified shadow map and conersion matrix +void opengl33_renderer::setup_shadow_map(opengl_texture *tex, renderpass_config conf) +{ + if (tex) + tex->bind(gl::MAX_TEXTURES + 0); + else + opengl_texture::unbind(gl::MAX_TEXTURES + 0); + + if (tex) + { + glm::mat4 coordmove; + + if (GLAD_GL_ARB_clip_control || GLAD_GL_EXT_clip_control) + // transform 1..-1 NDC xy coordinates to 1..0 + coordmove = glm::mat4( // + 0.5, 0.0, 0.0, 0.0, // + 0.0, 0.5, 0.0, 0.0, // + 0.0, 0.0, 1.0, 0.0, // + 0.5, 0.5, 0.0, 1.0 // + ); + else + // without clip_control we also need to transform z + coordmove = glm::mat4( // + 0.5, 0.0, 0.0, 0.0, // + 0.0, 0.5, 0.0, 0.0, // + 0.0, 0.0, 0.5, 0.0, // + 0.5, 0.5, 0.5, 1.0 // + ); + glm::mat4 depthproj = conf.pass_camera.projection(); + 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_ubo->update(scene_ubs); + } +} + +void opengl33_renderer::setup_env_map(gl::cubemap *tex) +{ + if (tex) + { + tex->bind(GL_TEXTURE0 + gl::MAX_TEXTURES + 1); + glActiveTexture(GL_TEXTURE0); + } + else + { + glActiveTexture(GL_TEXTURE0 + gl::MAX_TEXTURES + 1); + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); + glActiveTexture(GL_TEXTURE0); + } + opengl_texture::reset_unit_cache(); +} + +void opengl33_renderer::setup_environment_light(TEnvironmentType const Environment) +{ + + switch (Environment) + { + case e_flat: + { + m_sunlight.apply_intensity(); + // m_environment = Environment; + break; + } + case e_canyon: + { + m_sunlight.apply_intensity(0.4f); + // m_environment = Environment; + break; + } + case e_tunnel: + { + m_sunlight.apply_intensity(0.2f); + // m_environment = Environment; + break; + } + default: + { + break; + } + } +} + +bool opengl33_renderer::Render(world_environment *Environment) +{ + + // calculate shadow tone, based on positions of celestial bodies + m_shadowcolor = interpolate(glm::vec4{colors::shadow}, glm::vec4{colors::white}, clamp(-Environment->m_sun.getAngle(), 0.f, 6.f) / 6.f); + if ((Environment->m_sun.getAngle() < -18.f) && (Environment->m_moon.getAngle() > 0.f)) + { + // turn on moon shadows after nautical twilight, if the moon is actually up + m_shadowcolor = colors::shadow; + } + // soften shadows depending on sky overcast factor + m_shadowcolor = glm::min(colors::white, m_shadowcolor + ((colors::white - colors::shadow) * Global.Overcast)); + + if (Global.bWireFrame) + { + // bez nieba w trybie rysowania linii + return false; + } + + Bind_Material(null_handle); + ::glDisable(GL_DEPTH_TEST); + ::glPushMatrix(); + + model_ubs.set_modelview(OpenGLMatrices.data(GL_MODELVIEW)); + model_ubo->update(model_ubs); + + // skydome + // drawn with 500m radius to blend in if the fog range is low + glPushMatrix(); + glScalef(500.0f, 500.0f, 500.0f); + 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(); + + // stars + if (Environment->m_stars.m_stars != nullptr) + { + // setup + ::glPushMatrix(); + ::glRotatef(Environment->m_stars.m_latitude, 1.f, 0.f, 0.f); // ustawienie osi OY na północ + ::glRotatef(-std::fmod((float)Global.fTimeAngleDeg, 360.f), 0.f, 1.f, 0.f); // obrót dobowy osi OX + + // render + Render(Environment->m_stars.m_stars, nullptr, 1.0); + + // post-render cleanup + ::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); + + // 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(); + + model_ubs.param[0] = color; + model_ubs.param[1] = glm::vec4(glm::vec3(modelview * glm::vec4(sunvector, 1.0f)), 0.00463f); + 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); + } + // moon + { + Bind_Texture(0, m_moontexture); + glm::vec3 mooncolor(255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f); + glm::vec4 color(mooncolor.r, mooncolor.g, mooncolor.b, + // fade the moon if it's near the sun in the sky, especially during the day + std::max(0.f, 1.0 - 0.5 * Global.fLuminance - 0.65 * std::max(0.f, glm::dot(Environment->m_sun.getDirection(), Environment->m_moon.getDirection()))) * fogfactor); + + auto const moonvector = Environment->m_moon.getDirection(); + + // choose the moon appearance variant, based on current moon phase + // NOTE: implementation specific, 8 variants are laid out in 3x3 arrangement + // from new moon onwards, top left to right bottom (last spot is left for future use, if any) + auto const moonphase = Environment->m_moon.getPhase(); + float moonu, moonv; + if (moonphase < 1.84566f) + { + moonv = 1.0f - 0.0f; + moonu = 0.0f; + } + else if (moonphase < 5.53699f) + { + moonv = 1.0f - 0.0f; + moonu = 0.333f; + } + else if (moonphase < 9.22831f) + { + moonv = 1.0f - 0.0f; + moonu = 0.667f; + } + else if (moonphase < 12.91963f) + { + moonv = 1.0f - 0.333f; + moonu = 0.0f; + } + else if (moonphase < 16.61096f) + { + moonv = 1.0f - 0.333f; + moonu = 0.333f; + } + else if (moonphase < 20.30228f) + { + moonv = 1.0f - 0.333f; + moonu = 0.667f; + } + else if (moonphase < 23.99361f) + { + moonv = 1.0f - 0.667f; + moonu = 0.0f; + } + else if (moonphase < 27.68493f) + { + moonv = 1.0f - 0.667f; + moonu = 0.333f; + } + else + { + moonv = 1.0f - 0.0f; + moonu = 0.0f; + } + + model_ubs.param[0] = color; + model_ubs.param[1] = glm::vec4(glm::vec3(modelview * glm::vec4(moonvector, 1.0f)), 0.00451f); + model_ubs.param[2] = glm::vec4(moonu, moonv, 0.333f, 0.0f); + model_ubo->update(model_ubs); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + + gl::program::unbind(); + gl::vao::unbind(); + ::glPopMatrix(); + ::glEnable(GL_DEPTH_TEST); + + m_sunlight.apply_angle(); + m_sunlight.apply_intensity(); + + return true; +} + +// geometry methods +// creates a new geometry bank. returns: handle to the bank or NULL +gfx::geometrybank_handle opengl33_renderer::Create_Bank() +{ + + return m_geometry.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 opengl33_renderer::Insert(gfx::vertex_array &Vertices, gfx::geometrybank_handle const &Geometry, int const Type) +{ + gfx::calculate_tangent(Vertices, Type); + + return m_geometry.create_chunk(Vertices, Geometry, Type); +} + +// replaces data of specified chunk with the supplied vertex data, starting from specified offset +bool opengl33_renderer::Replace(gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, int const Type, std::size_t const Offset) +{ + gfx::calculate_tangent(Vertices, Type); + + return m_geometry.replace(Vertices, Geometry, Offset); +} + +// adds supplied vertex data at the end of specified chunk +bool opengl33_renderer::Append(gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, int const Type) +{ + gfx::calculate_tangent(Vertices, Type); + + return m_geometry.append(Vertices, Geometry); +} + +// provides direct access to vertex data of specfied chunk +gfx::vertex_array const &opengl33_renderer::Vertices(gfx::geometry_handle const &Geometry) const +{ + + return m_geometry.vertices(Geometry); +} + +// material methods +material_handle opengl33_renderer::Fetch_Material(std::string const &Filename, bool const Loadnow) +{ + + return m_materials.create(Filename, Loadnow); +} + +std::shared_ptr opengl33_renderer::Fetch_Shader(const std::string &name) +{ + auto it = m_shaders.find(name); + if (it == m_shaders.end()) + { + gl::shader fragment("mat_" + name + ".frag"); + gl::program *program = new gl::program({fragment, *m_vertex_shader.get()}); + m_shaders.insert({name, std::shared_ptr(program)}); + } + + return m_shaders[name]; +} + +void opengl33_renderer::Bind_Material(material_handle const Material, TSubModel *sm) +{ + if (Material != null_handle) + { + auto &material = m_materials.material(Material); + + memcpy(&model_ubs.param[0], &material.params[0], sizeof(model_ubs.param)); + + for (size_t i = 0; i < material.params_state.size(); i++) + { + gl::shader::param_entry entry = material.params_state[i]; + + glm::vec4 src(1.0f); + + if (sm) + { + if (entry.defaultparam == gl::shader::defaultparam_e::ambient) + src = sm->f4Ambient; + else if (entry.defaultparam == gl::shader::defaultparam_e::diffuse) + src = sm->f4Diffuse; + else if (entry.defaultparam == gl::shader::defaultparam_e::specular) + src = sm->f4Specular; + } + + for (size_t j = 0; j < entry.size; j++) + model_ubs.param[entry.location][entry.offset + j] = src[j]; + } + + if (m_blendingenabled) + { + model_ubs.opacity = -1.0f; + } + else + { + if (!std::isnan(material.opacity)) + model_ubs.opacity = material.opacity; + else + model_ubs.opacity = 0.5f; + } + + if (sm) + model_ubs.alpha_mult = sm->fVisible; + else + model_ubs.alpha_mult = 1.0f; + + if (GLAD_GL_ARB_multi_bind) + { + GLuint lastdiff = 0; + size_t i; + for (i = 0; i < gl::MAX_TEXTURES; i++) + if (material.textures[i] != null_handle) + { + opengl_texture &tex = m_textures.mark_as_used(material.textures[i]); + tex.create(); + if (opengl_texture::units[i] != tex.id) + { + opengl_texture::units[i] = tex.id; + lastdiff = i + 1; + } + } + else + break; + + if (lastdiff) + glBindTextures(0, lastdiff, &opengl_texture::units[0]); + } + else + { + size_t unit = 0; + for (auto &tex : material.textures) + { + if (tex == null_handle) + break; + m_textures.bind(unit, tex); + unit++; + } + } + + material.shader->bind(); + } + else if (Material != m_invalid_material) + Bind_Material(m_invalid_material); +} + +void opengl33_renderer::Bind_Material_Shadow(material_handle const Material) +{ + if (Material != null_handle) + { + auto &material = m_materials.material(Material); + + if (material.textures[0] != null_handle) + { + m_textures.bind(0, material.textures[0]); + m_alpha_shadow_shader->bind(); + } + else + m_shadow_shader->bind(); + } + else + m_shadow_shader->bind(); +} + +opengl_material const &opengl33_renderer::Material(material_handle const Material) const +{ + return m_materials.material(Material); +} + +opengl_material &opengl33_renderer::Material(material_handle const Material) +{ + return m_materials.material(Material); +} + +texture_handle opengl33_renderer::Fetch_Texture(std::string const &Filename, bool const Loadnow, GLint format_hint) +{ + return m_textures.create(Filename, Loadnow, format_hint); +} + +void opengl33_renderer::Bind_Texture(std::size_t const Unit, texture_handle const Texture) +{ + m_textures.bind(Unit, Texture); +} + +opengl_texture &opengl33_renderer::Texture(texture_handle const Texture) +{ + + return m_textures.texture(Texture); +} + +opengl_texture const &opengl33_renderer::Texture(texture_handle const Texture) const +{ + + return m_textures.texture(Texture); +} + +void opengl33_renderer::Update_AnimModel(TAnimModel *model) +{ + model->RaAnimate(m_framestamp); +} + +void opengl33_renderer::Render(scene::basic_region *Region) +{ + + m_sectionqueue.clear(); + m_cellqueue.clear(); + // build a list of region sections to render + glm::vec3 const cameraposition{m_renderpass.pass_camera.position()}; + auto const camerax = static_cast(std::floor(cameraposition.x / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2)); + auto const cameraz = static_cast(std::floor(cameraposition.z / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2)); + int const segmentcount = 2 * static_cast(std::ceil(m_renderpass.draw_range * Global.fDistanceFactor / scene::EU07_SECTIONSIZE)); + int const originx = camerax - segmentcount / 2; + int const originz = cameraz - segmentcount / 2; + + for (int row = originz; row <= originz + segmentcount; ++row) + { + if (row < 0) + { + continue; + } + if (row >= scene::EU07_REGIONSIDESECTIONCOUNT) + { + break; + } + for (int column = originx; column <= originx + segmentcount; ++column) + { + if (column < 0) + { + continue; + } + if (column >= scene::EU07_REGIONSIDESECTIONCOUNT) + { + break; + } + auto *section{Region->m_sections[row * scene::EU07_REGIONSIDESECTIONCOUNT + column]}; + if ((section != nullptr) && (m_renderpass.pass_camera.visible(section->m_area))) + { + m_sectionqueue.emplace_back(section); + } + } + } + + switch (m_renderpass.draw_mode) + { + case rendermode::color: + { + Update_Lights(simulation::Lights); + + Render(std::begin(m_sectionqueue), std::end(m_sectionqueue)); + // draw queue is filled while rendering sections + if (EditorModeFlag && m_current_viewport->main) + { + // 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(); + } + Render(std::begin(m_cellqueue), std::end(m_cellqueue)); + break; + } + case rendermode::shadows: + case rendermode::pickscenery: + { + // these render modes don't bother with lights + Render(std::begin(m_sectionqueue), std::end(m_sectionqueue)); + // they can also skip queue sorting, as they only deal with opaque geometry + // NOTE: there's benefit from rendering front-to-back, but is it significant enough? TODO: investigate + Render(std::begin(m_cellqueue), std::end(m_cellqueue)); + break; + } + case rendermode::reflections: + { + // for the time being reflections render only terrain geometry + Render(std::begin(m_sectionqueue), std::end(m_sectionqueue)); + break; + } + case rendermode::pickcontrols: + default: + { + // no need to render anything ourside of the cab in control picking mode + break; + } + } +} + +void opengl33_renderer::Render(section_sequence::iterator First, section_sequence::iterator Last) +{ + + switch (m_renderpass.draw_mode) + { + case rendermode::color: + case rendermode::reflections: + case rendermode::shadows: + break; + case rendermode::pickscenery: + { + // non-interactive scenery elements get neutral colour + model_ubs.param[0] = colors::none; + break; + } + default: + break; + } + + while (First != Last) + { + + auto *section = *First; + section->create_geometry(); + + // render shapes held by the section + switch (m_renderpass.draw_mode) + { + case rendermode::color: + case rendermode::reflections: + case rendermode::shadows: + case rendermode::pickscenery: + { + if (false == section->m_shapes.empty()) + { + // since all shapes of the section share center point we can optimize out a few calls here + ::glPushMatrix(); + auto const originoffset{section->m_area.center - m_renderpass.pass_camera.position()}; + ::glTranslated(originoffset.x, originoffset.y, originoffset.z); + // render + for (auto const &shape : section->m_shapes) + { + Render(shape, true); + } + // post-render cleanup + ::glPopMatrix(); + } + break; + } + case rendermode::pickcontrols: + default: + { + break; + } + } + + // add the section's cells to the cell queue + switch (m_renderpass.draw_mode) + { + case rendermode::color: + case rendermode::shadows: + case rendermode::pickscenery: + { + for (auto &cell : section->m_cells) + { + if ((true == cell.m_active) && (m_renderpass.pass_camera.visible(cell.m_area))) + { + // store visible cells with content as well as their current distance, for sorting later + m_cellqueue.emplace_back(glm::length2(m_renderpass.pass_camera.position() - cell.m_area.center), &cell); + } + } + break; + } + case rendermode::reflections: + case rendermode::pickcontrols: + default: + { + break; + } + } + // proceed to next section + ++First; + } + + switch (m_renderpass.draw_mode) + { + case rendermode::shadows: + { + break; + } + default: + { + break; + } + } +} + +void opengl33_renderer::Render(cell_sequence::iterator First, cell_sequence::iterator Last) +{ + + // cache initial iterator for the second sweep + auto first{First}; + // first pass draws elements which we know are located in section banks, to reduce vbo switching + while (First != Last) + { + + auto *cell = First->second; + // przeliczenia animacji torów w sektorze + cell->RaAnimate(m_framestamp); + + switch (m_renderpass.draw_mode) + { + case rendermode::color: + { + // since all shapes of the section share center point we can optimize out a few calls here + ::glPushMatrix(); + auto const originoffset{cell->m_area.center - m_renderpass.pass_camera.position()}; + ::glTranslated(originoffset.x, originoffset.y, originoffset.z); + + // render + // opaque non-instanced shapes + for (auto const &shape : cell->m_shapesopaque) + { + Render(shape, false); + } + // tracks + // TODO: update after path node refactoring + Render(std::begin(cell->m_paths), std::end(cell->m_paths)); + // post-render cleanup + ::glPopMatrix(); + + break; + } + case rendermode::shadows: + { + // since all shapes of the section share center point we can optimize out a few calls here + ::glPushMatrix(); + auto const originoffset{cell->m_area.center - m_renderpass.pass_camera.position()}; + ::glTranslated(originoffset.x, originoffset.y, originoffset.z); + + // render + // opaque non-instanced shapes + for (auto const &shape : cell->m_shapesopaque) + Render(shape, false); + // tracks + Render(std::begin(cell->m_paths), std::end(cell->m_paths)); + + // post-render cleanup + ::glPopMatrix(); + + break; + } + case rendermode::pickscenery: + { + // same procedure like with regular render, but editor-enabled nodes receive custom colour used for picking + // since all shapes of the section share center point we can optimize out a few calls here + ::glPushMatrix(); + auto const originoffset{cell->m_area.center - m_renderpass.pass_camera.position()}; + ::glTranslated(originoffset.x, originoffset.y, originoffset.z); + // render + // opaque non-instanced shapes + // non-interactive scenery elements get neutral colour + model_ubs.param[0] = colors::none; + for (auto const &shape : cell->m_shapesopaque) + Render(shape, false); + // tracks + for (auto *path : cell->m_paths) + { + model_ubs.param[0] = glm::vec4(pick_color(m_picksceneryitems.size() + 1), 1.0f); + Render(path); + } + // post-render cleanup + ::glPopMatrix(); + break; + } + case rendermode::reflections: + case rendermode::pickcontrols: + default: + { + break; + } + } + + ++First; + } + // second pass draws elements with their own vbos + while (first != Last) + { + + auto const *cell = first->second; + + switch (m_renderpass.draw_mode) + { + case rendermode::color: + case rendermode::shadows: + { + // opaque parts of instanced models + for (auto *instance : cell->m_instancesopaque) + { + Render(instance); + } + // opaque parts of vehicles + for (auto *path : cell->m_paths) + { + for (auto *dynamic : path->Dynamics) + { + Render(dynamic); + } + } + break; + } + case rendermode::pickscenery: + { + // opaque parts of instanced models + // same procedure like with regular render, but each node receives custom colour used for picking + for (auto *instance : cell->m_instancesopaque) + { + model_ubs.param[0] = glm::vec4(pick_color(m_picksceneryitems.size() + 1), 1.0f); + Render(instance); + } + // vehicles aren't included in scenery picking for the time being + break; + } + case rendermode::reflections: + case rendermode::pickcontrols: + default: + { + break; + } + } + + ++first; + } +} + +void opengl33_renderer::Draw_Geometry(std::vector::iterator begin, std::vector::iterator end) +{ + m_geometry.draw(begin, end); +} + +void opengl33_renderer::Draw_Geometry(const gfx::geometrybank_handle &handle) +{ + m_geometry.draw(handle); +} + +void opengl33_renderer::draw(const gfx::geometry_handle &handle) +{ + model_ubs.set_modelview(OpenGLMatrices.data(GL_MODELVIEW)); + model_ubo->update(model_ubs); + + m_geometry.draw(handle); +} + +void opengl33_renderer::draw(std::vector::iterator it, std::vector::iterator end) +{ + model_ubs.set_modelview(OpenGLMatrices.data(GL_MODELVIEW)); + model_ubo->update(model_ubs); + + Draw_Geometry(it, end); +} + +void opengl33_renderer::Render(scene::shape_node const &Shape, bool const Ignorerange) +{ + auto const &data{Shape.data()}; + + if (false == Ignorerange) + { + double distancesquared; + switch (m_renderpass.draw_mode) + { + case rendermode::shadows: + { + // 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees + distancesquared = Math3D::SquareMagnitude((data.area.center - m_renderpass.viewport_camera.position()) / (double)Global.ZoomFactor) / Global.fDistanceFactor; + break; + } + default: + { + distancesquared = glm::length2((data.area.center - m_renderpass.pass_camera.position()) / (double)Global.ZoomFactor) / Global.fDistanceFactor; + break; + } + } + if ((distancesquared < data.rangesquared_min) || (distancesquared >= data.rangesquared_max)) + { + return; + } + } + + // setup + switch (m_renderpass.draw_mode) + { + case rendermode::color: + case rendermode::reflections: + Bind_Material(data.material); + break; + case rendermode::shadows: + Bind_Material_Shadow(data.material); + break; + case rendermode::pickscenery: + case rendermode::pickcontrols: + m_pick_shader->bind(); + break; + default: + break; + } + // render + + draw(data.geometry); + // debug data + ++m_debugstats.shapes; + ++m_debugstats.drawcalls; +} + +void opengl33_renderer::Render(TAnimModel *Instance) +{ + + if (false == Instance->m_visible) + { + return; + } + + double distancesquared; + switch (m_renderpass.draw_mode) + { + case rendermode::shadows: + { + // 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees + distancesquared = Math3D::SquareMagnitude((Instance->location() - m_renderpass.viewport_camera.position()) / (double)Global.ZoomFactor) / Global.fDistanceFactor; + break; + } + default: + { + distancesquared = Math3D::SquareMagnitude((Instance->location() - m_renderpass.pass_camera.position()) / (double)Global.ZoomFactor) / Global.fDistanceFactor; + break; + } + } + if ((distancesquared < Instance->m_rangesquaredmin) || (distancesquared >= Instance->m_rangesquaredmax)) + { + return; + } + + switch (m_renderpass.draw_mode) + { + case rendermode::pickscenery: + { + // add the node to the pick list + m_picksceneryitems.emplace_back(Instance); + break; + } + default: + { + break; + } + } + + Instance->RaAnimate(m_framestamp); // jednorazowe przeliczenie animacji + Instance->RaPrepare(); + if (Instance->pModel) + { + // renderowanie rekurencyjne submodeli + Render(Instance->pModel, Instance->Material(), distancesquared, Instance->location() - m_renderpass.pass_camera.position(), Instance->vAngle); + } +} + +bool opengl33_renderer::Render(TDynamicObject *Dynamic) +{ + glDebug("Render TDynamicObject"); + + if (!Global.render_cab && Global.pCamera.m_owner == Dynamic) + return false; + + Dynamic->renderme = m_renderpass.pass_camera.visible(Dynamic); + if (false == Dynamic->renderme) + { + return false; + } + // debug data + ++m_debugstats.dynamics; + + // setup + TSubModel::iInstance = reinterpret_cast(Dynamic); //żeby nie robić cudzych animacji + glm::dvec3 const originoffset = Dynamic->vPosition - m_renderpass.pass_camera.position(); + // lod visibility ranges are defined for base (x 1.0) viewing distance. for render we adjust them for actual range multiplier and zoom + float squaredistance; + switch (m_renderpass.draw_mode) + { + case rendermode::shadows: + { + squaredistance = glm::length2(glm::vec3{glm::dvec3{Dynamic->vPosition - m_renderpass.viewport_camera.position()}} / Global.ZoomFactor) / Global.fDistanceFactor; + break; + } + default: + { + squaredistance = glm::length2(glm::vec3{originoffset} / Global.ZoomFactor) / Global.fDistanceFactor; + break; + } + } + Dynamic->ABuLittleUpdate(squaredistance); // ustawianie zmiennych submodeli dla wspólnego modelu + + glm::mat4 future_stack = model_ubs.future; + + glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW); + model_ubs.future *= glm::translate(mv, glm::vec3(Dynamic->get_future_movement())) * glm::inverse(mv); + + ::glPushMatrix(); + ::glTranslated(originoffset.x, originoffset.y, originoffset.z); + ::glMultMatrixd(Dynamic->mMatrix.getArray()); + + switch (m_renderpass.draw_mode) + { + + case rendermode::color: + { + if (Dynamic->fShade > 0.0f) + { + // change light level based on light level of the occupied track + m_sunlight.apply_intensity(Dynamic->fShade); + } + + // render + if (Dynamic->mdLowPolyInt) + Render(Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance); + + if (Dynamic->mdModel) + Render(Dynamic->mdModel, Dynamic->Material(), squaredistance); + + if (Dynamic->mdLoad) // renderowanie nieprzezroczystego ładunku + Render(Dynamic->mdLoad, Dynamic->Material(), squaredistance, {0.f, Dynamic->LoadOffset, 0.f}, {}); + + // post-render cleanup + + if (Dynamic->fShade > 0.0f) + { + // restore regular light level + m_sunlight.apply_intensity(); + } + break; + } + case rendermode::shadows: + { + if (Dynamic->mdLowPolyInt) + { + // low poly interior + Render(Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance); + } + if (Dynamic->mdModel) + Render(Dynamic->mdModel, Dynamic->Material(), squaredistance); + if (Dynamic->mdLoad) // renderowanie nieprzezroczystego ładunku + Render(Dynamic->mdLoad, Dynamic->Material(), squaredistance, {0.f, Dynamic->LoadOffset, 0.f}, {}); + // post-render cleanup + break; + } + case rendermode::pickcontrols: + { + if (Dynamic->mdLowPolyInt) { + // low poly interior + Render(Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance); + } + break; + } + case rendermode::pickscenery: + default: + { + break; + } + } + + ::glPopMatrix(); + + model_ubs.future = future_stack; + + // TODO: check if this reset is needed. In theory each object should render all parts based on its own instance data anyway? + if (Dynamic->btnOn) + Dynamic->TurnOff(); // przywrócenie domyślnych pozycji submodeli + + return true; +} + +// rendering kabiny gdy jest oddzielnym modelem i ma byc wyswietlana +bool opengl33_renderer::Render_cab(TDynamicObject const *Dynamic, float const Lightlevel, bool const Alpha) +{ + + if (Dynamic == nullptr) + { + + TSubModel::iInstance = 0; + return false; + } + + TSubModel::iInstance = reinterpret_cast(Dynamic); + + if ((true == FreeFlyModeFlag) || (false == Dynamic->bDisplayCab) || (Dynamic->mdKabina == Dynamic->mdModel)) + { + // ABu: Rendering kabiny jako ostatniej, zeby bylo widac przez szyby, tylko w widoku ze srodka + return false; + } + + if (Dynamic->mdKabina) + { // bo mogła zniknąć przy przechodzeniu do innego pojazdu + // setup shared by all render paths + ::glPushMatrix(); + + auto const originoffset = Dynamic->GetPosition() - m_renderpass.pass_camera.position(); + ::glTranslated(originoffset.x, originoffset.y, originoffset.z); + ::glMultMatrixd(Dynamic->mMatrix.readArray()); + + switch (m_renderpass.draw_mode) + { + case rendermode::color: + { + // render path specific setup: + if (Dynamic->fShade > 0.0f) + { + // change light level based on light level of the occupied track + m_sunlight.apply_intensity(Dynamic->fShade); + } + + // crude way to light the cabin, until we have something more complete in place + glm::vec3 old_ambient = light_ubs.ambient; + light_ubs.ambient += Dynamic->InteriorLight * Lightlevel; + light_ubo->update(light_ubs); + + // render + if (true == Alpha) + { + // translucent parts + Render_Alpha(Dynamic->mdKabina, Dynamic->Material(), 0.0); + } + else + { + // opaque parts + Render(Dynamic->mdKabina, Dynamic->Material(), 0.0); + } + // post-render restore + if (Dynamic->fShade > 0.0f) + { + // change light level based on light level of the occupied track + m_sunlight.apply_intensity(); + } + + // restore ambient + light_ubs.ambient = old_ambient; + light_ubo->update(light_ubs); + + break; + } + case rendermode::cabshadows: + if (true == Alpha) + // translucent parts + Render_Alpha(Dynamic->mdKabina, Dynamic->Material(), 0.0); + else + // opaque parts + Render(Dynamic->mdKabina, Dynamic->Material(), 0.0); + break; + case rendermode::pickcontrols: + { + Render(Dynamic->mdKabina, Dynamic->Material(), 0.0); + break; + } + default: + { + break; + } + } + // post-render restore + ::glPopMatrix(); + } + + return true; +} + +bool opengl33_renderer::Render(TModel3d *Model, material_data const *Material, float const Squaredistance) +{ + + auto alpha = (Material != nullptr ? Material->textures_alpha : 0x30300030); + alpha ^= 0x0F0F000F; // odwrócenie flag tekstur, aby wyłapać nieprzezroczyste + if (0 == (alpha & Model->iFlags & 0x1F1F001F)) + { + // czy w ogóle jest co robić w tym cyklu? + return false; + } + + Model->Root->fSquareDist = Squaredistance; // zmienna globalna! + + // setup + Model->Root->ReplacableSet((Material != nullptr ? Material->replacable_skins : nullptr), alpha); + + Model->Root->pRoot = Model; + + // render + Render(Model->Root); + + // debug data + ++m_debugstats.models; + + // post-render cleanup + + return true; +} + +bool opengl33_renderer::Render(TModel3d *Model, material_data const *Material, float const Squaredistance, Math3D::vector3 const &Position, glm::vec3 const &A) +{ + Math3D::vector3 Angle(A); + ::glPushMatrix(); + ::glTranslated(Position.x, Position.y, Position.z); + if (Angle.y != 0.0) + ::glRotated(Angle.y, 0.0, 1.0, 0.0); + if (Angle.x != 0.0) + ::glRotated(Angle.x, 1.0, 0.0, 0.0); + if (Angle.z != 0.0) + ::glRotated(Angle.z, 0.0, 0.0, 1.0); + + auto const result = Render(Model, Material, Squaredistance); + + ::glPopMatrix(); + + return result; +} + +void opengl33_renderer::Render(TSubModel *Submodel) +{ + glDebug("Render TSubModel"); + + if ((Submodel->iVisible) && (TSubModel::fSquareDist >= Submodel->fSquareMinDist) && (TSubModel::fSquareDist < Submodel->fSquareMaxDist)) + { + + // debug data + ++m_debugstats.submodels; + ++m_debugstats.drawcalls; + + glm::mat4 future_stack = model_ubs.future; + + if (Submodel->iFlags & 0xC000) + { + ::glPushMatrix(); + if (Submodel->fMatrix) + ::glMultMatrixf(Submodel->fMatrix->readArray()); + if (Submodel->b_aAnim != TAnimType::at_None) + { + Submodel->RaAnimation(Submodel->b_aAnim); + + glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW); + model_ubs.future *= (mv * Submodel->future_transform) * glm::inverse(mv); + } + } + + if (Submodel->eType < TP_ROTATOR) + { + // renderowanie obiektów OpenGL + if (Submodel->iAlpha & Submodel->iFlags & 0x1F) + { + // rysuj gdy element nieprzezroczysty + switch (m_renderpass.draw_mode) + { + case rendermode::color: + case rendermode::reflections: + { + // material configuration: + // transparency hack + if (Submodel->fVisible < 1.0f) + setup_drawing(true); + + // textures... + if (Submodel->m_material < 0) + { // zmienialne skóry + Bind_Material(Submodel->ReplacableSkinId[-Submodel->m_material], Submodel); + } + else + { + // również 0 + Bind_Material(Submodel->m_material, Submodel); + } + + // ...luminance + auto const isemissive { ( Submodel->f4Emision.a > 0.f ) && ( Global.fLuminance < Submodel->fLight ) }; + if (isemissive) + model_ubs.emission = Submodel->f4Emision.a; + + // main draw call + draw(Submodel->m_geometry); + + // post-draw reset + model_ubs.emission = 0.0f; + if (Submodel->fVisible < 1.0f) + setup_drawing(false); + + break; + } + case rendermode::shadows: + case rendermode::cabshadows: + { + if (Submodel->m_material < 0) + { // zmienialne skóry + Bind_Material_Shadow(Submodel->ReplacableSkinId[-Submodel->m_material]); + } + else + { + // również 0 + Bind_Material_Shadow(Submodel->m_material); + } + draw(Submodel->m_geometry); + break; + } + case rendermode::pickscenery: + { + m_pick_shader->bind(); + draw(Submodel->m_geometry); + break; + } + case rendermode::pickcontrols: + { + m_pick_shader->bind(); + // control picking applies individual colour for each submodel + m_pickcontrolsitems.emplace_back(Submodel); + model_ubs.param[0] = glm::vec4(pick_color(m_pickcontrolsitems.size()), 1.0f); + draw(Submodel->m_geometry); + break; + } + default: + { + break; + } + } + } + } + else if (Submodel->eType == TP_STARS) + { + + switch (m_renderpass.draw_mode) + { + // colour points are only rendered in colour mode(s) + case rendermode::color: + case rendermode::reflections: + { + if (Global.fLuminance < Submodel->fLight) + { + Bind_Material(Submodel->m_material, Submodel); + + // main draw call + model_ubs.param[1].x = 2.0f * 2.0f; + + draw(Submodel->m_geometry); + } + break; + } + default: + { + break; + } + } + } + if (Submodel->Child != nullptr) + if (Submodel->iAlpha & Submodel->iFlags & 0x001F0000) + Render(Submodel->Child); + + if (Submodel->iFlags & 0xC000) + { + model_ubs.future = future_stack; + ::glPopMatrix(); + } + } + /* + if( Submodel->b_Anim < at_SecondsJump ) + Submodel->b_Anim = at_None; // wyłączenie animacji dla kolejnego użycia subm + */ + if (Submodel->Next) + if (Submodel->iAlpha & Submodel->iFlags & 0x1F000000) + Render(Submodel->Next); // dalsze rekurencyjnie +} + +void opengl33_renderer::Render(TTrack *Track) +{ + if ((Track->m_material1 == 0) && (Track->m_material2 == 0) && (Track->eType != tt_Switch || Track->SwitchExtension->m_material3 == 0)) + { + return; + } + if (false == Track->m_visible) + { + return; + } + + ++m_debugstats.paths; + ++m_debugstats.drawcalls; + + switch (m_renderpass.draw_mode) + { + // single path pieces are rendererd in pick scenery mode only + case rendermode::pickscenery: + { + m_picksceneryitems.emplace_back(Track); + model_ubs.param[0] = glm::vec4(pick_color(m_picksceneryitems.size() + 1), 1.0f); + m_pick_shader->bind(); + + draw(std::begin(Track->Geometry1), std::end(Track->Geometry1)); + draw(std::begin(Track->Geometry2), std::end(Track->Geometry2)); + if (Track->eType == tt_Switch) + draw(Track->SwitchExtension->Geometry3); + break; + } + default: + { + break; + } + } +} + +// experimental, does track rendering in two passes, to take advantage of reduced texture switching +void opengl33_renderer::Render(scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last) +{ + + // setup + switch (m_renderpass.draw_mode) + { + case rendermode::shadows: + { + // NOTE: roads-based platforms tend to miss parts of shadows if rendered with either back or front culling + glDisable(GL_CULL_FACE); + break; + } + default: + { + break; + } + } + + // TODO: render auto generated trackbeds together with regular trackbeds in pass 1, and all rails in pass 2 + // first pass, material 1 + for (auto first{First}; first != Last; ++first) + { + + auto const track{*first}; + + if (track->m_material1 == 0) + { + continue; + } + if (false == track->m_visible) + { + continue; + } + + ++m_debugstats.paths; + ++m_debugstats.drawcalls; + + switch (m_renderpass.draw_mode) + { + case rendermode::color: + case rendermode::reflections: + { + if (track->eEnvironment != e_flat) + { + setup_environment_light(track->eEnvironment); + } + Bind_Material(track->m_material1); + draw(std::begin(track->Geometry1), std::end(track->Geometry1)); + if (track->eEnvironment != e_flat) + { + // restore default lighting + setup_environment_light(); + } + break; + } + case rendermode::shadows: + { + if ((std::abs(track->fTexHeight1) < 0.35f) || (track->iCategoryFlag != 2)) + { + // shadows are only calculated for high enough roads, typically meaning track platforms + continue; + } + Bind_Material_Shadow(track->m_material1); + draw(std::begin(track->Geometry1), std::end(track->Geometry1)); + break; + } + case rendermode::pickscenery: // pick scenery mode uses piece-by-piece approach + case rendermode::pickcontrols: + default: + { + break; + } + } + } + // second pass, material 2 + for (auto first{First}; first != Last; ++first) + { + auto const track{*first}; + + if (track->m_material2 == 0) + { + continue; + } + if (false == track->m_visible) + { + continue; + } + + switch (m_renderpass.draw_mode) + { + case rendermode::color: + case rendermode::reflections: + { + if (track->eEnvironment != e_flat) + { + setup_environment_light(track->eEnvironment); + } + Bind_Material(track->m_material2); + draw(std::begin(track->Geometry2), std::end(track->Geometry2)); + if (track->eEnvironment != e_flat) + { + // restore default lighting + setup_environment_light(); + } + break; + } + case rendermode::shadows: + { + if ((std::abs(track->fTexHeight1) < 0.35f) || ((track->iCategoryFlag == 1) && (track->eType != tt_Normal))) + { + // shadows are only calculated for high enough trackbeds + continue; + } + Bind_Material_Shadow(track->m_material2); + draw(std::begin(track->Geometry2), std::end(track->Geometry2)); + break; + } + case rendermode::pickscenery: // pick scenery mode uses piece-by-piece approach + case rendermode::pickcontrols: + default: + { + break; + } + } + } + + // third pass, material 3 + for (auto first{First}; first != Last; ++first) + { + + auto const track{*first}; + + if (track->eType != tt_Switch) + { + continue; + } + if (track->SwitchExtension->m_material3 == 0) + { + continue; + } + if (false == track->m_visible) + { + continue; + } + + switch (m_renderpass.draw_mode) + { + case rendermode::color: + case rendermode::reflections: + { + if (track->eEnvironment != e_flat) + { + setup_environment_light(track->eEnvironment); + } + Bind_Material(track->SwitchExtension->m_material3); + draw(track->SwitchExtension->Geometry3); + if (track->eEnvironment != e_flat) + { + // restore default lighting + setup_environment_light(); + } + break; + } + case rendermode::shadows: + { + if ((std::abs(track->fTexHeight1) < 0.35f) || ((track->iCategoryFlag == 1) && (track->eType != tt_Normal))) + { + // shadows are only calculated for high enough trackbeds + continue; + } + Bind_Material_Shadow(track->SwitchExtension->m_material3); + draw(track->SwitchExtension->Geometry3); + break; + } + case rendermode::pickscenery: // pick scenery mode uses piece-by-piece approach + case rendermode::pickcontrols: + default: + { + break; + } + } + } + + // post-render reset + switch (m_renderpass.draw_mode) + { + case rendermode::shadows: + { + // restore standard face cull mode + ::glEnable(GL_CULL_FACE); + break; + } + default: + { + break; + } + } +} + +void opengl33_renderer::Render(TMemCell *Memcell) +{ + + ::glPushMatrix(); + auto const position = Memcell->location() - m_renderpass.pass_camera.position(); + ::glTranslated(position.x, position.y + 0.5, position.z); + + switch (m_renderpass.draw_mode) + { + case rendermode::color: + { + break; + } + case rendermode::shadows: + case rendermode::pickscenery: + { + break; + } + case rendermode::reflections: + case rendermode::pickcontrols: + { + break; + } + default: + { + break; + } + } + + ::glPopMatrix(); +} + +void opengl33_renderer::Render_particles() +{ + model_ubs.set_modelview(OpenGLMatrices.data(GL_MODELVIEW)); + model_ubo->update(model_ubs); + + Bind_Texture(0, m_smoketexture); + m_particlerenderer.update(m_renderpass.pass_camera); + m_particlerenderer.render(); +} + +void opengl33_renderer::Render_precipitation() +{ + if (Global.Overcast <= 1.f) + { + return; + } + + ::glPushMatrix(); + // tilt the precipitation cone against the velocity vector for crude motion blur + // include current wind vector while at it + auto const velocity { + simulation::Environment.m_precipitation.m_cameramove * -1.0 + + glm::dvec3{ simulation::Environment.wind() } * 0.5 }; + if (glm::length2(velocity) > 0.0) + { + auto const forward{glm::normalize(velocity)}; + auto left{glm::cross(forward, {0.0, 1.0, 0.0})}; + auto const rotationangle{std::min(45.0, (FreeFlyModeFlag ? 5 * glm::length(velocity) : simulation::Train->Dynamic()->GetVelocity() * 0.2))}; + ::glRotated(rotationangle, left.x, 0.0, left.z); + } + if (false == FreeFlyModeFlag) + { + // counter potential vehicle roll + auto const roll{0.5 * glm::degrees(simulation::Train->Dynamic()->Roll())}; + if (roll != 0.0) + { + auto const forward{simulation::Train->Dynamic()->VectorFront()}; + auto const vehicledirection = simulation::Train->Dynamic()->DirectionGet(); + ::glRotated(roll, forward.x, 0.0, forward.z); + } + } + if (!Global.iPause) + { + if (Global.Weather == "rain:") + // oddly enough random streaks produce more natural looking rain than ones the eye can follow + m_precipitationrotation = LocalRandom() * 360; + else + m_precipitationrotation = 0.0; + } + + ::glRotated(m_precipitationrotation, 0.0, 1.0, 0.0); + + model_ubs.set_modelview(OpenGLMatrices.data(GL_MODELVIEW)); + model_ubs.param[0] = interpolate(0.5f * (Global.DayLight.diffuse + Global.DayLight.ambient), colors::white, 0.5f * clamp(Global.fLuminance, 0.f, 1.f)); + model_ubs.param[1].x = simulation::Environment.m_precipitation.get_textureoffset(); + model_ubo->update(model_ubs); + + m_precipitationrenderer.update(); + m_precipitationrenderer.render(); + + ::glPopMatrix(); +} + +void opengl33_renderer::Render_Alpha(scene::basic_region *Region) +{ + + // sort the nodes based on their distance to viewer + std::sort(std::begin(m_cellqueue), std::end(m_cellqueue), [](distancecell_pair const &Left, distancecell_pair const &Right) { return (Left.first) < (Right.first); }); + + Render_Alpha(std::rbegin(m_cellqueue), std::rend(m_cellqueue)); +} + +void opengl33_renderer::Render_Alpha(cell_sequence::reverse_iterator First, cell_sequence::reverse_iterator Last) +{ + + // NOTE: this method is launched only during color pass therefore we don't bother with mode test here + // first pass draws elements which we know are located in section banks, to reduce vbo switching + { + auto first{First}; + while (first != Last) + { + + auto const *cell = first->second; + + if (false == cell->m_shapestranslucent.empty()) + { + // since all shapes of the cell share center point we can optimize out a few calls here + ::glPushMatrix(); + auto const originoffset{cell->m_area.center - m_renderpass.pass_camera.position()}; + ::glTranslated(originoffset.x, originoffset.y, originoffset.z); + // render + // NOTE: we can reuse the method used to draw opaque geometry + for (auto const &shape : cell->m_shapestranslucent) + { + Render(shape, false); + } + // post-render cleanup + ::glPopMatrix(); + } + + ++first; + } + } + // second pass draws elements with their own vbos + { + auto first{First}; + while (first != Last) + { + + auto const *cell = first->second; + + // translucent parts of instanced models + for (auto *instance : cell->m_instancetranslucent) + { + Render_Alpha(instance); + } + // translucent parts of vehicles + for (auto *path : cell->m_paths) + { + for (auto *dynamic : path->Dynamics) + { + Render_Alpha(dynamic); + } + } + + ++first; + } + } + // third pass draws the wires; + // wires use section vbos, but for the time being we want to draw them at the very end + { + auto first{First}; + while (first != Last) + { + + auto const *cell = first->second; + + if ((false == cell->m_traction.empty() || (false == cell->m_lines.empty()))) + { + // since all shapes of the cell share center point we can optimize out a few calls here + ::glPushMatrix(); + auto const originoffset{cell->m_area.center - m_renderpass.pass_camera.position()}; + ::glTranslated(originoffset.x, originoffset.y, originoffset.z); + Bind_Material(null_handle); + // render + for (auto *traction : cell->m_traction) + { + Render_Alpha(traction); + } + for (auto &lines : cell->m_lines) + { + Render_Alpha(lines); + } + // post-render cleanup + ::glPopMatrix(); + } + + ++first; + } + } +} + +void opengl33_renderer::Render_Alpha(TAnimModel *Instance) +{ + + if (false == Instance->m_visible) + { + return; + } + + double distancesquared; + switch (m_renderpass.draw_mode) + { + case rendermode::shadows: + default: + { + distancesquared = glm::length2((Instance->location() - m_renderpass.pass_camera.position()) / (double)Global.ZoomFactor) / Global.fDistanceFactor; + break; + } + } + if ((distancesquared < Instance->m_rangesquaredmin) || (distancesquared >= Instance->m_rangesquaredmax)) + { + return; + } + + Instance->RaPrepare(); + if (Instance->pModel) + { + // renderowanie rekurencyjne submodeli + Render_Alpha(Instance->pModel, Instance->Material(), distancesquared, Instance->location() - m_renderpass.pass_camera.position(), Instance->vAngle); + } +} + +void opengl33_renderer::Render_Alpha(TTraction *Traction) +{ + glDebug("Render_Alpha TTraction"); + + auto const distancesquared { glm::length2( ( Traction->location() - m_renderpass.pass_camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor }; + if ((distancesquared < Traction->m_rangesquaredmin) || (distancesquared >= Traction->m_rangesquaredmax)) + { + return; + } + + if (false == Traction->m_visible) + { + return; + } + // rysuj jesli sa druty i nie zerwana + if ((Traction->Wires == 0) || (true == TestFlag(Traction->DamageFlag, 128))) + { + return; + } + // setup + auto const distance{static_cast(std::sqrt(distancesquared))}; + auto const linealpha = 20.f * Traction->WireThickness / std::max(0.5f * Traction->radius() + 1.f, distance - (0.5f * Traction->radius())); + if (m_widelines_supported) + glLineWidth(clamp(0.5f * linealpha + Traction->WireThickness * Traction->radius() / 1000.f, 1.f, 1.75f)); + + // render + + // McZapkie-261102: kolor zalezy od materialu i zasniedzenia + model_ubs.param[0] = glm::vec4(Traction->wire_color(), glm::min(1.0f, linealpha)); + + if (m_renderpass.draw_mode == rendermode::shadows) + Bind_Material_Shadow(null_handle); + else + m_line_shader->bind(); + + draw(Traction->m_geometry); + + // debug data + ++m_debugstats.traction; + ++m_debugstats.drawcalls; + + if (m_widelines_supported) + glLineWidth(1.0f); +} + +void opengl33_renderer::Render_Alpha(scene::lines_node const &Lines) +{ + glDebug("Render_Alpha scene::lines_node"); + + auto const &data{Lines.data()}; + + auto const distancesquared { glm::length2( ( data.area.center - m_renderpass.pass_camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor }; + if ((distancesquared < data.rangesquared_min) || (distancesquared >= data.rangesquared_max)) + { + return; + } + // setup + auto const distance{static_cast(std::sqrt(distancesquared))}; + auto const linealpha = + (data.line_width > 0.f ? 10.f * data.line_width / std::max(0.5f * data.area.radius + 1.f, distance - (0.5f * data.area.radius)) : 1.f); // negative width means the lines are always opague + if (m_widelines_supported) + glLineWidth(clamp(0.5f * linealpha + data.line_width * data.area.radius / 1000.f, 1.f, 8.f)); + + model_ubs.param[0] = glm::vec4(glm::vec3(data.lighting.diffuse * m_sunlight.ambient), glm::min(1.0f, linealpha)); + + if (m_renderpass.draw_mode == rendermode::shadows) + Bind_Material_Shadow(null_handle); + else + m_line_shader->bind(); + draw(data.geometry); + + ++m_debugstats.lines; + ++m_debugstats.drawcalls; + + if (m_widelines_supported) + glLineWidth(1.0f); +} + +bool opengl33_renderer::Render_Alpha(TDynamicObject *Dynamic) +{ + if (!Global.render_cab && Global.pCamera.m_owner == Dynamic) + return false; + + if (false == Dynamic->renderme) + { + return false; + } + + // setup + TSubModel::iInstance = (size_t)Dynamic; //żeby nie robić cudzych animacji + glm::dvec3 const originoffset = Dynamic->vPosition - m_renderpass.pass_camera.position(); + // lod visibility ranges are defined for base (x 1.0) viewing distance. for render we adjust them for actual range multiplier and zoom + float squaredistance; + switch (m_renderpass.draw_mode) + { + case rendermode::shadows: + default: + { + squaredistance = glm::length2(glm::vec3{originoffset} / Global.ZoomFactor) / Global.fDistanceFactor; + break; + } + } + Dynamic->ABuLittleUpdate(squaredistance); // ustawianie zmiennych submodeli dla wspólnego modelu + + glm::mat4 future_stack = model_ubs.future; + + glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW); + model_ubs.future *= glm::translate(mv, glm::vec3(Dynamic->get_future_movement())) * glm::inverse(mv); + + ::glPushMatrix(); + + ::glTranslated(originoffset.x, originoffset.y, originoffset.z); + ::glMultMatrixd(Dynamic->mMatrix.getArray()); + + if (Dynamic->fShade > 0.0f) + { + // change light level based on light level of the occupied track + m_sunlight.apply_intensity(Dynamic->fShade); + } + + // render + if (Dynamic->mdLowPolyInt) + { + // low poly interior + Render_Alpha(Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance); + } + + if (Dynamic->mdModel) + Render_Alpha(Dynamic->mdModel, Dynamic->Material(), squaredistance); + + if (Dynamic->mdLoad) // renderowanie nieprzezroczystego ładunku + Render_Alpha(Dynamic->mdLoad, Dynamic->Material(), squaredistance); + + // post-render cleanup + if (Dynamic->fShade > 0.0f) + { + // restore regular light level + m_sunlight.apply_intensity(); + } + + ::glPopMatrix(); + + model_ubs.future = future_stack; + + if (Dynamic->btnOn) + Dynamic->TurnOff(); // przywrócenie domyślnych pozycji submodeli + + return true; +} + +bool opengl33_renderer::Render_Alpha(TModel3d *Model, material_data const *Material, float const Squaredistance) +{ + + auto alpha = (Material != nullptr ? Material->textures_alpha : 0x30300030); + + if (0 == (alpha & Model->iFlags & 0x2F2F002F)) + { + // nothing to render + return false; + } + + Model->Root->fSquareDist = Squaredistance; // zmienna globalna! + + // setup + Model->Root->ReplacableSet((Material != nullptr ? Material->replacable_skins : nullptr), alpha); + + Model->Root->pRoot = Model; + + // render + Render_Alpha(Model->Root); + + // post-render cleanup + + return true; +} + +bool opengl33_renderer::Render_Alpha(TModel3d *Model, material_data const *Material, float const Squaredistance, Math3D::vector3 const &Position, glm::vec3 const &A) +{ + Math3D::vector3 Angle(A); + ::glPushMatrix(); + ::glTranslated(Position.x, Position.y, Position.z); + if (Angle.y != 0.0) + ::glRotated(Angle.y, 0.0, 1.0, 0.0); + if (Angle.x != 0.0) + ::glRotated(Angle.x, 1.0, 0.0, 0.0); + if (Angle.z != 0.0) + ::glRotated(Angle.z, 0.0, 0.0, 1.0); + + auto const result = Render_Alpha(Model, Material, Squaredistance); // position is effectively camera offset + + ::glPopMatrix(); + + return result; +} + +void opengl33_renderer::Render_Alpha(TSubModel *Submodel) +{ + // renderowanie przezroczystych przez DL + if ((Submodel->iVisible) && (TSubModel::fSquareDist >= Submodel->fSquareMinDist) && (TSubModel::fSquareDist < Submodel->fSquareMaxDist)) + { + + // debug data + ++m_debugstats.submodels; + ++m_debugstats.drawcalls; + + glm::mat4 future_stack = model_ubs.future; + + if (Submodel->iFlags & 0xC000) + { + ::glPushMatrix(); + if (Submodel->fMatrix) + ::glMultMatrixf(Submodel->fMatrix->readArray()); + if (Submodel->b_aAnim != TAnimType::at_None) + { + Submodel->RaAnimation(Submodel->b_aAnim); + + glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW); + model_ubs.future *= (mv * Submodel->future_transform) * glm::inverse(mv); + } + } + + if (Submodel->eType < TP_ROTATOR) + { + // renderowanie obiektów OpenGL + if (Submodel->iAlpha & Submodel->iFlags & 0x2F) + { + // rysuj gdy element przezroczysty + switch (m_renderpass.draw_mode) + { + case rendermode::color: + { + // material configuration: + // textures... + if (Submodel->m_material < 0) + { // zmienialne skóry + Bind_Material(Submodel->ReplacableSkinId[-Submodel->m_material], Submodel); + } + else + { + Bind_Material(Submodel->m_material, Submodel); + } + // ...luminance + + auto const isemissive { ( Submodel->f4Emision.a > 0.f ) && ( Global.fLuminance < Submodel->fLight ) }; + if (isemissive) + model_ubs.emission = Submodel->f4Emision.a; + + // main draw call + draw(Submodel->m_geometry); + + model_ubs.emission = 0.0f; + break; + } + case rendermode::cabshadows: + { + if (Submodel->m_material < 0) + { // zmienialne skóry + Bind_Material_Shadow(Submodel->ReplacableSkinId[-Submodel->m_material]); + } + else + { + Bind_Material_Shadow(Submodel->m_material); + } + draw(Submodel->m_geometry); + break; + } + default: + { + break; + } + } + } + } + else if (Submodel->eType == TP_FREESPOTLIGHT) + { + // NOTE: we're forced here to redo view angle calculations etc, because this data isn't instanced but stored along with the single mesh + // TODO: separate instance data from reusable geometry + auto const &modelview = OpenGLMatrices.data(GL_MODELVIEW); + auto const lightcenter = modelview * interpolate(glm::vec4(0.f, 0.f, -0.05f, 1.f), glm::vec4(0.f, 0.f, -0.25f, 1.f), + static_cast(TSubModel::fSquareDist / Submodel->fSquareMaxDist)); // pozycja punktu świecącego względem kamery + Submodel->fCosViewAngle = glm::dot(glm::normalize(modelview * glm::vec4(0.f, 0.f, -1.f, 1.f) - lightcenter), glm::normalize(-lightcenter)); + + if (Global.fLuminance < Submodel->fLight || Global.Overcast > 1.0f) + { + if (Submodel->fCosViewAngle > Submodel->fCosFalloffAngle) + { + // only bother if the viewer is inside the visibility cone + // luminosity at night is at level of ~0.1, so the overall resulting transparency in clear conditions is ~0.5 at full 'brightness' + auto glarelevel{clamp(std::max(0.6f - Global.fLuminance, // reduce the glare in bright daylight + Global.Overcast - 1.f), // ensure some glare in rainy/foggy conditions + 0.f, 1.f)}; + // view angle attenuation + float const anglefactor{clamp((Submodel->fCosViewAngle - Submodel->fCosFalloffAngle) / (Submodel->fCosHotspotAngle - Submodel->fCosFalloffAngle), 0.f, 1.f)}; + + glarelevel *= anglefactor; + + if (glarelevel > 0.0f) + { + glDisable(GL_DEPTH_TEST); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + + ::glPushMatrix(); + ::glLoadIdentity(); // macierz jedynkowa + ::glTranslatef(lightcenter.x, lightcenter.y, lightcenter.z); // początek układu zostaje bez zmian + ::glRotated(std::atan2(lightcenter.x, lightcenter.z) * 180.0 / M_PI, 0.0, 1.0, 0.0); // jedynie obracamy w pionie o kąt + + auto const lightcolor = glm::vec3(Submodel->DiffuseOverride.r < 0.f ? // -1 indicates no override + Submodel->f4Diffuse : + Submodel->DiffuseOverride); + + m_billboard_shader->bind(); + Bind_Texture(0, m_glaretexture); + model_ubs.param[0] = glm::vec4(glm::vec3(lightcolor), Submodel->fVisible * glarelevel); + + // main draw call + if (Submodel->occlusion_query) { + if (!Global.gfx_usegles) { + glBeginConditionalRender(*Submodel->occlusion_query, GL_QUERY_WAIT); + draw(m_billboardgeometry); + glEndConditionalRender(); + } + else { + auto result = Submodel->occlusion_query->result(); + if (result && *result) + draw(m_billboardgeometry); + } + } + else + draw(m_billboardgeometry); + + glEnable(GL_DEPTH_TEST); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + ::glPopMatrix(); + } + } + } + + if (Submodel->fCosViewAngle > Submodel->fCosFalloffAngle) + { + // kąt większy niż maksymalny stożek swiatła + float lightlevel = 1.f; // TODO, TBD: parameter to control light strength + // view angle attenuation + float const anglefactor = clamp((Submodel->fCosViewAngle - Submodel->fCosFalloffAngle) / (Submodel->fCosHotspotAngle - Submodel->fCosFalloffAngle), 0.f, 1.f); + lightlevel *= anglefactor; + + // distance attenuation. NOTE: since it's fixed pipeline with built-in gamma correction we're using linear attenuation + // we're capping how much effect the distance attenuation can have, otherwise the lights get too tiny at regular distances + float const distancefactor{std::max(0.5f, (Submodel->fSquareMaxDist - TSubModel::fSquareDist) / Submodel->fSquareMaxDist)}; + auto const pointsize{std::max(3.f, 5.f * distancefactor * anglefactor)}; + auto const resolutionratio { Global.iWindowHeight / 1080.f }; + // additionally reduce light strength for farther sources in rain or snow + if (Global.Overcast > 0.75f) + { + float const precipitationfactor{interpolate(interpolate(1.f, 0.25f, clamp(Global.Overcast * 0.75f - 0.5f, 0.f, 1.f)), 1.f, distancefactor)}; + lightlevel *= precipitationfactor; + } + + if (lightlevel > 0.f) + { + ::glPushMatrix(); + ::glLoadIdentity(); + ::glTranslatef(lightcenter.x, lightcenter.y, lightcenter.z); // początek układu zostaje bez zmian + + // material configuration: + // limit impact of dense fog on the lights + auto const lightrange { std::max( 500, m_fogrange * 2 ) }; // arbitrary, visibility at least 750m + model_ubs.fog_density = 1.0 / lightrange; + + // 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); + + m_freespot_shader->bind(); + + if (Global.Overcast > 1.0f) + { + // fake fog halo + float const fogfactor{interpolate(2.f, 1.f, clamp(Global.fFogEnd / 2000, 0.f, 1.f)) * std::max(1.f, Global.Overcast)}; + model_ubs.param[1].x = pointsize * resolutionratio * fogfactor * 2.0f; + model_ubs.param[0] = glm::vec4(glm::vec3(lightcolor), Submodel->fVisible * std::min(1.f, lightlevel) * 0.5f); + + draw(Submodel->m_geometry); + } + model_ubs.param[1].x = pointsize * resolutionratio * 2.0f; + model_ubs.param[0] = glm::vec4(glm::vec3(lightcolor), Submodel->fVisible * std::min(1.f, lightlevel)); + + if (!Submodel->occlusion_query) + Submodel->occlusion_query.emplace(gl::query::ANY_SAMPLES_PASSED); + Submodel->occlusion_query->begin(); + + draw(Submodel->m_geometry); + + Submodel->occlusion_query->end(); + + // post-draw reset + model_ubs.emission = 0.0f; + model_ubs.fog_density = 1.0f / m_fogrange; + + ::glPopMatrix(); + } + } + } + + if (Submodel->Child != nullptr) + { + if (Submodel->eType == TP_TEXT) + { // tekst renderujemy w specjalny sposób, zamiast submodeli z łańcucha Child + int i, j = (int)Submodel->pasText->size(); + TSubModel *p; + if (!Submodel->smLetter) + { // jeśli nie ma tablicy, to ją stworzyć; miejsce nieodpowiednie, ale tymczasowo może być + Submodel->smLetter = new TSubModel *[256]; // tablica wskaźników submodeli dla wyświetlania tekstu + memset(Submodel->smLetter, 0, 256 * sizeof(TSubModel *)); // wypełnianie zerami + p = Submodel->Child; + while (p) + { + Submodel->smLetter[p->pName[0]] = p; + p = p->Next; // kolejny znak + } + } + for (i = 1; i <= j; ++i) + { + p = Submodel->smLetter[(*(Submodel->pasText))[i]]; // znak do wyświetlenia + if (p) + { // na razie tylko jako przezroczyste + Render_Alpha(p); + if (p->fMatrix) + ::glMultMatrixf(p->fMatrix->readArray()); // przesuwanie widoku + } + } + } + else if (Submodel->iAlpha & Submodel->iFlags & 0x002F0000) + Render_Alpha(Submodel->Child); + } + + if (Submodel->iFlags & 0xC000) + { + model_ubs.future = future_stack; + ::glPopMatrix(); + } + } + /* + if( Submodel->b_aAnim < at_SecondsJump ) + Submodel->b_aAnim = at_None; // wyłączenie animacji dla kolejnego użycia submodelu + */ + if (Submodel->Next != nullptr) + if (Submodel->iAlpha & Submodel->iFlags & 0x2F000000) + Render_Alpha(Submodel->Next); +}; + +// utility methods +void opengl33_renderer::Update_Pick_Control() +{ + // context-switch workaround + gl::buffer::unbind(); + + if (!m_picking_pbo->is_busy()) + { + unsigned char pickreadout[4]; + if (m_picking_pbo->read_data(1, 1, pickreadout)) + { + auto const controlindex = pick_index(glm::ivec3{pickreadout[0], pickreadout[1], pickreadout[2]}); + TSubModel const *control{nullptr}; + if ((controlindex > 0) && (controlindex <= m_pickcontrolsitems.size())) + { + control = m_pickcontrolsitems[controlindex - 1]; + } + + m_pickcontrolitem = control; + + for (auto f : m_control_pick_requests) + f(m_pickcontrolitem); + m_control_pick_requests.clear(); + } + + if (!m_control_pick_requests.empty()) + { + // determine point to examine + glm::dvec2 mousepos = Application.get_cursor_pos(); + mousepos.y = Global.iWindowHeight - mousepos.y; // cursor coordinates are flipped compared to opengl + + glm::ivec2 pickbufferpos; + pickbufferpos = glm::ivec2{mousepos.x * EU07_PICKBUFFERSIZE / std::max(1, Global.iWindowWidth), mousepos.y * EU07_PICKBUFFERSIZE / std::max(1, Global.iWindowHeight)}; + pickbufferpos = glm::clamp(pickbufferpos, glm::ivec2(0, 0), glm::ivec2(EU07_PICKBUFFERSIZE - 1, EU07_PICKBUFFERSIZE - 1)); + + Render_pass(*m_viewports.front().get(), rendermode::pickcontrols); + m_pick_fb->bind(); + m_picking_pbo->request_read(pickbufferpos.x, pickbufferpos.y, 1, 1); + m_pick_fb->unbind(); + } + } +} + +void opengl33_renderer::Update_Pick_Node() +{ + if (!m_picking_node_pbo->is_busy()) + { + unsigned char pickreadout[4]; + if (m_picking_node_pbo->read_data(1, 1, pickreadout)) + { + auto const nodeindex = pick_index(glm::ivec3{pickreadout[0], pickreadout[1], pickreadout[2]}); + scene::basic_node *node{nullptr}; + if ((nodeindex > 0) && (nodeindex <= m_picksceneryitems.size())) + { + node = m_picksceneryitems[nodeindex - 1]; + } + + m_picksceneryitem = node; + + for (auto f : m_node_pick_requests) + f(m_picksceneryitem); + m_node_pick_requests.clear(); + } + + if (!m_node_pick_requests.empty()) + { + // determine point to examine + glm::dvec2 mousepos = Application.get_cursor_pos(); + mousepos.y = Global.iWindowHeight - mousepos.y; // cursor coordinates are flipped compared to opengl + + glm::ivec2 pickbufferpos; + pickbufferpos = glm::ivec2{mousepos.x * EU07_PICKBUFFERSIZE / std::max(1, Global.iWindowWidth), mousepos.y * EU07_PICKBUFFERSIZE / std::max(1, Global.iWindowHeight)}; + pickbufferpos = glm::clamp(pickbufferpos, glm::ivec2(0, 0), glm::ivec2(EU07_PICKBUFFERSIZE - 1, EU07_PICKBUFFERSIZE - 1)); + + Render_pass(*m_viewports.front().get(), rendermode::pickscenery); + m_pick_fb->bind(); + m_picking_node_pbo->request_read(pickbufferpos.x, pickbufferpos.y, 1, 1); + m_pick_fb->unbind(); + } + } +} + +void opengl33_renderer::pick_control(std::function callback) +{ + m_control_pick_requests.push_back(callback); +} + +void opengl33_renderer::pick_node(std::function callback) +{ + m_node_pick_requests.push_back(callback); +} + +glm::dvec3 opengl33_renderer::get_mouse_depth() +{ + if (!m_depth_pointer_pbo->is_busy()) + { + // determine point to examine + glm::dvec2 mousepos = Application.get_cursor_pos(); + mousepos.y = Global.iWindowHeight - mousepos.y; // cursor coordinates are flipped compared to opengl + + glm::ivec2 bufferpos; + bufferpos = glm::ivec2{mousepos.x * Global.gfx_framebuffer_width / std::max(1, Global.iWindowWidth), mousepos.y * Global.gfx_framebuffer_height / std::max(1, Global.iWindowHeight)}; + bufferpos = glm::clamp(bufferpos, glm::ivec2(0, 0), glm::ivec2(Global.gfx_framebuffer_width - 1, Global.gfx_framebuffer_height - 1)); + + float pointdepth = std::numeric_limits::max(); + + if (!Global.gfx_usegles) + { + m_depth_pointer_pbo->read_data(1, 1, &pointdepth, 4); + + if (!Global.iMultisampling) + { + m_depth_pointer_pbo->request_read(bufferpos.x, bufferpos.y, 1, 1, 4, GL_DEPTH_COMPONENT, GL_FLOAT); + } + else if (Global.gfx_skippipeline) + { + gl::framebuffer::blit(nullptr, m_depth_pointer_fb.get(), bufferpos.x, bufferpos.y, 1, 1, GL_DEPTH_BUFFER_BIT, 0); + + m_depth_pointer_fb->bind(); + m_depth_pointer_pbo->request_read(0, 0, 1, 1, 4, GL_DEPTH_COMPONENT, GL_FLOAT); + m_depth_pointer_fb->unbind(); + } + else + { + gl::framebuffer::blit(m_viewports.front()->msaa_fb.get(), m_depth_pointer_fb.get(), bufferpos.x, bufferpos.y, 1, 1, GL_DEPTH_BUFFER_BIT, 0); + + m_depth_pointer_fb->bind(); + m_depth_pointer_pbo->request_read(0, 0, 1, 1, 4, GL_DEPTH_COMPONENT, GL_FLOAT); + m_viewports.front()->msaa_fb->bind(); + } + } + else + { + unsigned int data[4]; + if (m_depth_pointer_pbo->read_data(1, 1, data, 16)) + pointdepth = (double)data[0] / 65535.0; + + if (Global.gfx_skippipeline) + { + gl::framebuffer::blit(nullptr, m_depth_pointer_fb.get(), 0, 0, Global.gfx_framebuffer_width, Global.gfx_framebuffer_height, GL_DEPTH_BUFFER_BIT, 0); + + m_empty_vao->bind(); + m_depth_pointer_tex->bind(0); + m_depth_pointer_shader->bind(); + m_depth_pointer_fb2->bind(); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + m_depth_pointer_pbo->request_read(bufferpos.x, bufferpos.y, 1, 1, 16, GL_RGBA_INTEGER, GL_UNSIGNED_INT); + m_depth_pointer_shader->unbind(); + m_empty_vao->unbind(); + m_depth_pointer_fb2->unbind(); + } + else + { + gl::framebuffer::blit(m_viewports.front()->msaa_fb.get(), m_depth_pointer_fb.get(), 0, 0, Global.gfx_framebuffer_width, Global.gfx_framebuffer_height, GL_DEPTH_BUFFER_BIT, 0); + + m_empty_vao->bind(); + m_depth_pointer_tex->bind(0); + m_depth_pointer_shader->bind(); + m_depth_pointer_fb2->bind(); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + m_depth_pointer_pbo->request_read(bufferpos.x, bufferpos.y, 1, 1, 16, GL_RGBA_INTEGER, GL_UNSIGNED_INT); + m_depth_pointer_shader->unbind(); + m_empty_vao->unbind(); + m_viewports.front()->msaa_fb->bind(); + } + } + + if (pointdepth != std::numeric_limits::max()) + { + if (GLAD_GL_ARB_clip_control || GLAD_GL_EXT_clip_control) { + if (pointdepth > 0.0f) + m_worldmousecoordinates = glm::unProjectZO(glm::vec3(bufferpos, pointdepth), glm::mat4(glm::mat3(m_colorpass.pass_camera.modelview())), m_colorpass.pass_camera.projection(), + glm::vec4(0, 0, Global.gfx_framebuffer_width, Global.gfx_framebuffer_height)); + } else if (pointdepth < 1.0f) + m_worldmousecoordinates = glm::unProjectNO(glm::vec3(bufferpos, pointdepth), glm::mat4(glm::mat3(m_colorpass.pass_camera.modelview())), m_colorpass.pass_camera.projection(), + glm::vec4(0, 0, Global.gfx_framebuffer_width, Global.gfx_framebuffer_height)); + } + } + + return m_colorpass.pass_camera.position() + glm::dvec3{m_worldmousecoordinates}; +} + +void opengl33_renderer::Update(double const Deltatime) +{ + Update_Pick_Control(); + Update_Pick_Node(); + + m_updateaccumulator += Deltatime; + + if (m_updateaccumulator < 1.0) + { + // too early for any work + return; + } + + m_updateaccumulator = 0.0; + 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 ((true == Global.ResourceSweep) && (true == simulation::is_ready)) + { + // garbage collection + m_geometry.update(); + m_textures.update(); + } + + if ((true == Global.ControlPicking) && (false == FreeFlyModeFlag)) + pick_control([](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 *) {}); + + // dump last opengl error, if any + auto const glerror = ::glGetError(); + if (glerror != GL_NO_ERROR) + { + std::string glerrorstring; + if (glerror == GL_INVALID_ENUM) + glerrorstring = "GL_INVALID_ENUM"; + else if (glerror == GL_INVALID_VALUE) + glerrorstring = "GL_INVALID_VALUE"; + else if (glerror == GL_INVALID_OPERATION) + glerrorstring = "GL_INVALID_OPERATION"; + else if (glerror == GL_OUT_OF_MEMORY) + glerrorstring = "GL_OUT_OF_MEMORY"; + else if (glerror == GL_INVALID_FRAMEBUFFER_OPERATION) + glerrorstring = "GL_INVALID_FRAMEBUFFER_OPERATION"; + Global.LastGLError = std::to_string(glerror) + " (" + glerrorstring + ")"; + } +} + +// debug performance string +std::string const &opengl33_renderer::info_times() const +{ + + return m_debugtimestext; +} + +std::string const &opengl33_renderer::info_stats() const +{ + + return m_debugstatstext; +} + +void opengl33_renderer::Update_Lights(light_array &Lights) +{ + glDebug("Update_Lights"); + + // arrange the light array from closest to farthest from current position of the camera + auto const camera = m_renderpass.pass_camera.position(); + std::sort(std::begin(Lights.data), std::end(Lights.data), [&camera](light_array::light_record const &Left, light_array::light_record const &Right) { + // move lights which are off at the end... + if (Left.intensity == 0.f) + { + return false; + } + if (Right.intensity == 0.f) + { + return true; + } + // ...otherwise prefer closer and/or brigher light sources + return (glm::length2(camera - Left.position) * (1.f - Left.intensity)) < (glm::length2(camera - Right.position) * (1.f - Right.intensity)); + }); + + auto renderlight = m_lights.begin(); + size_t light_i = 1; + + glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW); + + for (auto const &scenelight : Lights.data) + { + + if (renderlight == m_lights.end()) + { + // we ran out of lights to assign + break; + } + if (scenelight.intensity == 0.f) + { + // all lights past this one are bound to be off + break; + } + auto const lightoffset = glm::vec3{scenelight.position - camera}; + if (glm::length(lightoffset) > 1000.f) + { + // we don't care about lights past arbitrary limit of 1 km. + // but there could still be weaker lights which are closer, so keep looking + continue; + } + // if the light passed tests so far, it's good enough + renderlight->position = lightoffset; + renderlight->direction = scenelight.direction; + + auto luminance = static_cast(Global.fLuminance); + // adjust luminance level based on vehicle's location, e.g. tunnels + auto const environment = scenelight.owner->fShade; + if (environment > 0.f) + { + luminance *= environment; + } + 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_angle(); + + gl::light_element_ubs *l = &light_ubs.lights[light_i]; + l->pos = mv * glm::vec4(renderlight->position, 1.0f); + l->dir = mv * glm::vec4(renderlight->direction, 0.0f); + l->type = gl::light_element_ubs::SPOT; + l->in_cutoff = headlight_config.in_cutoff; + l->out_cutoff = headlight_config.out_cutoff; + l->color = renderlight->diffuse * renderlight->factor; + l->linear = headlight_config.falloff_linear / 10.0f; + l->quadratic = headlight_config.falloff_quadratic / 100.0f; + l->ambient = headlight_config.ambient; + l->intensity = headlight_config.intensity; + light_i++; + + ++renderlight; + } + + light_ubs.ambient = m_sunlight.ambient * m_sunlight.factor; + 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].ambient = 0.0f; + light_ubs.lights[0].intensity = 1.0f; + light_ubs.lights_count = light_i; + + light_ubs.fog_color = Global.FogColor; + if (Global.fFogEnd > 0) + { + m_fogrange = Global.fFogEnd / std::max(1.f, Global.Overcast * 2.f); + model_ubs.fog_density = 1.0f / m_fogrange; + } + else + model_ubs.fog_density = 0.0f; + + model_ubo->update(model_ubs); + light_ubo->update(light_ubs); +} + +bool opengl33_renderer::Init_caps() +{ + WriteLog("MaSzyna OpenGL Renderer"); + WriteLog("Renderer: " + std::string((char *)glGetString(GL_RENDERER))); + WriteLog("Vendor: " + std::string((char *)glGetString(GL_VENDOR))); + WriteLog("GL version: " + std::string((char *)glGetString(GL_VERSION))); + + WriteLog("--------"); + + GLint extCount = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &extCount); + + WriteLog("Supported extensions:"); + for (int i = 0; i < extCount; i++) + { + const char *ext = (const char *)glGetStringi(GL_EXTENSIONS, i); + WriteLog(ext); + } + WriteLog("--------"); + + if (!Global.gfx_usegles) + { + if (!GLAD_GL_VERSION_3_3) + { + ErrorLog("requires OpenGL >= 3.3!"); + return false; + } + + if (!GLAD_GL_EXT_texture_sRGB) + ErrorLog("EXT_texture_sRGB not supported!"); + + if (!GLAD_GL_EXT_texture_compression_s3tc) + ErrorLog("EXT_texture_compression_s3tc not supported!"); + + if (GLAD_GL_ARB_texture_filter_anisotropic) + WriteLog("ARB_texture_filter_anisotropic supported!"); + + if (GLAD_GL_ARB_multi_bind) + WriteLog("ARB_multi_bind supported!"); + + if (GLAD_GL_ARB_direct_state_access) + WriteLog("ARB_direct_state_access supported!"); + + if (GLAD_GL_ARB_clip_control) + WriteLog("ARB_clip_control supported!"); + } + else + { + if (!GLAD_GL_ES_VERSION_3_0) + { + ErrorLog("requires OpenGL ES >= 3.0!"); + return false; + } + + if (GLAD_GL_EXT_texture_filter_anisotropic) + WriteLog("EXT_texture_filter_anisotropic supported!"); + + if (GLAD_GL_EXT_clip_control) + WriteLog("EXT_clip_control supported!"); + + if (GLAD_GL_EXT_geometry_shader) + WriteLog("EXT_geometry_shader supported!"); + } + + glGetError(); + glLineWidth(2.0f); + if (!glGetError()) + { + WriteLog("wide lines supported!"); + m_widelines_supported = true; + } + else + WriteLog("warning: wide lines not supported"); + + WriteLog("--------"); + + // ograniczenie maksymalnego rozmiaru tekstur - parametr dla skalowania tekstur + { + GLint texturesize; + ::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texturesize); + Global.iMaxTextureSize = std::min(Global.iMaxTextureSize, texturesize); + WriteLog("texture sizes capped at " + std::to_string(Global.iMaxTextureSize) + "px"); + m_shadowbuffersize = Global.shadowtune.map_size; + m_shadowbuffersize = std::min(m_shadowbuffersize, texturesize); + WriteLog("shadows map size capped at " + std::to_string(m_shadowbuffersize) + "px"); + } + Global.DynamicLightCount = std::min(Global.DynamicLightCount, 8); + + if (Global.iMultisampling) + { + WriteLog("using multisampling x" + std::to_string(1 << Global.iMultisampling)); + } + + if (Global.gfx_framebuffer_width == -1) + Global.gfx_framebuffer_width = Global.iWindowWidth; + if (Global.gfx_framebuffer_height == -1) + Global.gfx_framebuffer_height = Global.iWindowHeight; + + WriteLog("main window size: " + std::to_string(Global.gfx_framebuffer_width) + "x" + std::to_string(Global.gfx_framebuffer_height)); + + return true; +} + +glm::vec3 opengl33_renderer::pick_color(std::size_t const Index) +{ + return glm::vec3{((Index & 0xff0000) >> 16) / 255.0f, ((Index & 0x00ff00) >> 8) / 255.0f, (Index & 0x0000ff) / 255.0f}; +} + +std::size_t opengl33_renderer::pick_index(glm::ivec3 const &Color) +{ + return Color.b + (Color.g * 256) + (Color.r * 256 * 256); +} + +//--------------------------------------------------------------------------- \ No newline at end of file diff --git a/opengl33renderer.h b/opengl33renderer.h new file mode 100644 index 00000000..a4c8e408 --- /dev/null +++ b/opengl33renderer.h @@ -0,0 +1,377 @@ +/* +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 "renderer.h" +#include "openglcamera.h" +#include "opengl33light.h" +#include "opengl33particles.h" +#include "openglskydome.h" +#include "opengl33precipitation.h" +#include "simulationenvironment.h" +#include "scene.h" +#include "MemCell.h" +#include "lightarray.h" +#include "gl/ubo.h" +#include "gl/framebuffer.h" +#include "gl/renderbuffer.h" +#include "gl/postfx.h" +#include "gl/shader.h" +#include "gl/cubemap.h" +#include "gl/glsl_common.h" +#include "gl/pbo.h" +#include "gl/query.h" + +// bare-bones render controller, in lack of anything better yet +class opengl33_renderer : public gfx_renderer { + + public: + // 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 + texture_handle + Fetch_Texture( std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA ) override; + void + Bind_Texture( texture_handle const Texture ) override; + void + Bind_Texture( std::size_t const Unit, texture_handle const Texture ) override; + opengl_texture & + Texture( texture_handle const Texture ) override; + opengl_texture const & + Texture( texture_handle const Texture ) const override; + // utility methods + 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; + + void pick_control(std::function callback); + void pick_node(std::function callback); + + // members + GLenum static const sunlight{0}; + std::size_t m_drawcount{0}; + + bool debug_ui_active = false; + + private: + // types + enum class rendermode + { + none, + color, + shadows, + cabshadows, + reflections, + pickcontrols, + pickscenery + }; + + struct debug_stats + { + int dynamics{0}; + int models{0}; + int submodels{0}; + int paths{0}; + int traction{0}; + int shapes{0}; + int lines{0}; + int drawcalls{0}; + }; + + using section_sequence = std::vector; + using distancecell_pair = std::pair; + using cell_sequence = std::vector; + + struct renderpass_config + { + opengl_camera pass_camera; + opengl_camera viewport_camera; + rendermode draw_mode{rendermode::none}; + float draw_range{0.0f}; + }; + + struct viewport_config { + int width; + int height; + + float draw_range; + + bool main = false; + GLFWwindow *window = nullptr; + + glm::mat4 camera_transform; + + std::unique_ptr msaa_fb; + std::unique_ptr msaa_rbc; + std::unique_ptr msaa_rbv; + std::unique_ptr msaa_rbd; + + std::unique_ptr main_fb; + std::unique_ptr main_texv; + std::unique_ptr main_tex; + + std::unique_ptr main2_fb; + std::unique_ptr main2_tex; + }; + + viewport_config *m_current_viewport = nullptr; + + typedef std::vector opengllight_array; + + // methods + std::unique_ptr make_shader(std::string v, std::string f); + bool Init_caps(); + void setup_pass(viewport_config &Viewport, renderpass_config &Config, rendermode const Mode, float const Znear = 0.f, float const Zfar = 1.f, bool const Ignoredebug = false); + void setup_matrices(); + void setup_drawing(bool const Alpha = false); + void setup_shadow_map(opengl_texture *tex, renderpass_config conf); + void setup_env_map(gl::cubemap *tex); + void setup_environment_light(TEnvironmentType const Environment = e_flat); + // runs jobs needed to generate graphics for specified render pass + void Render_pass(viewport_config &vp, rendermode const Mode); + // creates dynamic environment cubemap + bool Render_reflections(viewport_config &vp); + bool Render(world_environment *Environment); + void Render(scene::basic_region *Region); + void Render(section_sequence::iterator First, section_sequence::iterator Last); + void Render(cell_sequence::iterator First, cell_sequence::iterator Last); + void Render(scene::shape_node const &Shape, bool const Ignorerange); + void Render(TAnimModel *Instance); + bool Render(TDynamicObject *Dynamic); + bool Render(TModel3d *Model, material_data const *Material, float const Squaredistance, Math3D::vector3 const &Position, glm::vec3 const &Angle); + bool Render(TModel3d *Model, material_data const *Material, float const Squaredistance); + void Render(TSubModel *Submodel); + void Render(TTrack *Track); + void Render(scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last); + bool Render_cab(TDynamicObject const *Dynamic, float const Lightlevel, bool const Alpha = false); + void Render(TMemCell *Memcell); + void Render_particles(); + void Render_precipitation(); + void Render_Alpha(scene::basic_region *Region); + void Render_Alpha(cell_sequence::reverse_iterator First, cell_sequence::reverse_iterator Last); + void Render_Alpha(TAnimModel *Instance); + void Render_Alpha(TTraction *Traction); + void Render_Alpha(scene::lines_node const &Lines); + bool Render_Alpha(TDynamicObject *Dynamic); + bool Render_Alpha(TModel3d *Model, material_data const *Material, float const Squaredistance, Math3D::vector3 const &Position, glm::vec3 const &Angle); + bool Render_Alpha(TModel3d *Model, material_data const *Material, float const Squaredistance); + void Render_Alpha(TSubModel *Submodel); + void Update_Lights(light_array &Lights); + glm::vec3 pick_color(std::size_t const Index); + std::size_t pick_index(glm::ivec3 const &Color); + + bool init_viewport(viewport_config &vp); + + void draw(const gfx::geometry_handle &handle); + void draw(std::vector::iterator begin, std::vector::iterator end); + + void draw_debug_ui(); + + // members + GLFWwindow *m_window{nullptr}; // main window + gfx::geometrybank_manager m_geometry; + material_manager m_materials; + texture_manager m_textures; + opengl33_light m_sunlight; + opengllight_array m_lights; + /* + float m_sunandviewangle; // cached dot product of sunlight and camera vectors + */ + gfx::geometry_handle m_billboardgeometry{0, 0}; + texture_handle m_glaretexture{-1}; + texture_handle m_suntexture{-1}; + texture_handle m_moontexture{-1}; + texture_handle m_smoketexture{-1}; + + // main shadowmap resources + int m_shadowbuffersize{2048}; + glm::mat4 m_shadowtexturematrix; // conversion from camera-centric world space to light-centric clip space + glm::mat4 m_cabshadowtexturematrix; // conversion from cab-centric world space to light-centric clip space + + 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_precipitation m_precipitationrenderer; + opengl33_particles m_particlerenderer; // particle visualization subsystem + + unsigned int m_framestamp; // id of currently rendered gfx frame + float m_framerate; + double m_updateaccumulator{0.0}; + std::string m_debugtimestext; + std::string m_pickdebuginfo; + debug_stats m_debugstats; + std::string m_debugstatstext; + + glm::vec4 m_baseambient{0.0f, 0.0f, 0.0f, 1.0f}; + glm::vec4 m_shadowcolor{colors::shadow}; + // TEnvironmentType m_environment { e_flat }; + float m_specularopaquescalefactor{1.f}; + float m_speculartranslucentscalefactor{1.f}; + + float m_fogrange = 2000.0f; + + renderpass_config m_renderpass; // parameters for current render pass + section_sequence m_sectionqueue; // list of sections in current render pass + cell_sequence m_cellqueue; + renderpass_config m_colorpass; // parametrs of most recent color pass + renderpass_config m_shadowpass; // parametrs of most recent shadowmap pass + renderpass_config m_cabshadowpass; // parameters of most recent cab shadowmap pass + std::vector m_pickcontrolsitems; + TSubModel const *m_pickcontrolitem{nullptr}; + std::vector m_picksceneryitems; + scene::basic_node *m_picksceneryitem{nullptr}; + glm::vec3 m_worldmousecoordinates { 0.f }; +#ifdef EU07_USE_DEBUG_CAMERA + renderpass_config m_worldcamera; // debug item +#endif + + std::optional m_timequery; + GLuint64 m_gllasttime = 0; + + double m_precipitationrotation; + + glm::mat4 perspective_projection(float fov, float aspect, float z_near, float z_far); + glm::mat4 perpsective_frustumtest_projection(float fov, float aspect, float z_near, float z_far); + glm::mat4 ortho_projection(float left, float right, float bottom, float top, float z_near, float z_far); + glm::mat4 ortho_frustumtest_projection(float left, float right, float bottom, float top, float z_near, float z_far); + + std::vector> m_control_pick_requests; + std::vector> m_node_pick_requests; + + std::unique_ptr m_vertex_shader; + + std::unique_ptr scene_ubo; + std::unique_ptr model_ubo; + std::unique_ptr light_ubo; + gl::scene_ubs scene_ubs; + gl::model_ubs model_ubs; + gl::light_ubs light_ubs; + + std::unordered_map> m_shaders; + + std::unique_ptr m_line_shader; + std::unique_ptr m_freespot_shader; + std::unique_ptr m_billboard_shader; + std::unique_ptr m_celestial_shader; + + std::unique_ptr m_empty_vao; + + std::vector> m_viewports; + + std::unique_ptr m_pfx_motionblur; + std::unique_ptr m_pfx_tonemapping; + + std::unique_ptr m_shadow_shader; + std::unique_ptr m_alpha_shadow_shader; + + std::unique_ptr m_pick_fb; + std::unique_ptr m_pick_tex; + std::unique_ptr m_pick_rb; + std::unique_ptr m_pick_shader; + + std::unique_ptr m_empty_cubemap; + + std::unique_ptr m_cabshadows_fb; + std::unique_ptr m_cabshadows_tex; + + std::unique_ptr m_env_fb; + std::unique_ptr m_env_rb; + std::unique_ptr m_env_tex; + + std::unique_ptr m_shadow_fb; + std::unique_ptr m_shadow_tex; + + std::unique_ptr m_picking_pbo; + std::unique_ptr m_picking_node_pbo; + + std::unique_ptr m_depth_pointer_pbo; + std::unique_ptr m_depth_pointer_fb; + std::unique_ptr m_depth_pointer_fb2; + std::unique_ptr m_depth_pointer_rb; + std::unique_ptr m_depth_pointer_tex; + std::unique_ptr m_depth_pointer_shader; + + material_handle m_invalid_material; + + bool m_blendingenabled; + + bool m_widelines_supported; + + struct headlight_config_s + { + float in_cutoff = 1.005f; + float out_cutoff = 0.993f; + + float falloff_linear = 0.069f; + float falloff_quadratic = 0.03f; + + float intensity = 1.0f; + float ambient = 0.184f; + }; + + headlight_config_s headlight_config; +}; + +//--------------------------------------------------------------------------- \ No newline at end of file diff --git a/openglcamera.h b/openglcamera.h index 2429a67f..ed4c87a4 100644 --- a/openglcamera.h +++ b/openglcamera.h @@ -9,7 +9,6 @@ http://mozilla.org/MPL/2.0/. #pragma once -#include "GL/glew.h" #include "frustum.h" #include "scene.h" @@ -23,6 +22,10 @@ public: inline void update_frustum() { update_frustum( m_projection, m_modelview ); } + inline + void + update_frustum(glm::mat4 frustumtest_proj) { + update_frustum(frustumtest_proj, m_modelview); } void update_frustum( glm::mat4 const &Projection, glm::mat4 const &Modelview ); bool diff --git a/openglgeometrybank.cpp b/openglgeometrybank.cpp index bddc2f8b..1b0d6058 100644 --- a/openglgeometrybank.cpp +++ b/openglgeometrybank.cpp @@ -162,14 +162,14 @@ opengl_vbogeometrybank::bind_streams( gfx::stream_units const &Units, unsigned i } // NOTE: normal and color streams share the data, making them effectively mutually exclusive if( Streams & gfx::stream::normal ) { - ::glNormalPointer( GL_FLOAT, sizeof( gfx::basic_vertex ), reinterpret_cast( sizeof( float ) * 3 ) ); + ::glNormalPointer( GL_FLOAT, sizeof( gfx::basic_vertex ), reinterpret_cast( 12 ) ); ::glEnableClientState( GL_NORMAL_ARRAY ); } else { ::glDisableClientState( GL_NORMAL_ARRAY ); } if( Streams & gfx::stream::color ) { - ::glColorPointer( 3, GL_FLOAT, sizeof( gfx::basic_vertex ), reinterpret_cast( sizeof( float ) * 3 ) ); + ::glColorPointer( 3, GL_FLOAT, sizeof( gfx::basic_vertex ), reinterpret_cast( 12 ) ); ::glEnableClientState( GL_COLOR_ARRAY ); } else { diff --git a/opengllight.h b/opengllight.h index a746861e..64880201 100644 --- a/opengllight.h +++ b/opengllight.h @@ -9,7 +9,6 @@ http://mozilla.org/MPL/2.0/. #pragma once -#include "GL/glew.h" #include "light.h" struct opengl_light : public basic_light { diff --git a/openglmatrixstack.h b/openglmatrixstack.h index b37a1532..8444b96f 100644 --- a/openglmatrixstack.h +++ b/openglmatrixstack.h @@ -14,10 +14,6 @@ http://mozilla.org/MPL/2.0/. #include #include #include -#include "GL/glew.h" -#ifdef _WIN32 -#include "GL/wglew.h" -#endif // encapsulation of the fixed pipeline opengl matrix stack class opengl_matrices { diff --git a/openglparticles.h b/openglparticles.h index 2d464795..afb18a29 100644 --- a/openglparticles.h +++ b/openglparticles.h @@ -9,8 +9,6 @@ http://mozilla.org/MPL/2.0/. #pragma once -#include "GL/glew.h" - class opengl_camera; // particle data visualizer diff --git a/openglprecipitation.cpp b/openglprecipitation.cpp new file mode 100644 index 00000000..68a71604 --- /dev/null +++ b/openglprecipitation.cpp @@ -0,0 +1,171 @@ +/* +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 "openglprecipitation.h" + +#include "Globals.h" +#include "renderer.h" +#include "simulationenvironment.h" + +opengl_precipitation::~opengl_precipitation() { + + if( m_vertexbuffer != 0 ) { ::glDeleteBuffers( 1, &m_vertexbuffer ); } + if( m_indexbuffer != 0 ) { ::glDeleteBuffers( 1, &m_indexbuffer ); } + if( m_uvbuffer != 0 ) { ::glDeleteBuffers( 1, &m_uvbuffer ); } +} + +void +opengl_precipitation::create( int const Tesselation ) { + + m_vertices.clear(); + m_uvs.clear(); + m_indices.clear(); + + auto const heightfactor { 10.f }; // height-to-radius factor + auto const verticaltexturestretchfactor { 1.5f }; // crude motion blur + + // create geometry chunk + auto const latitudes { 3 }; // just a cylinder with end cones + auto const longitudes { Tesselation }; + auto const longitudehalfstep { 0.5f * static_cast( 2.0 * M_PI * 1.f / longitudes ) }; // for crude uv correction + + std::uint16_t index = 0; + +// auto const radius { 25.f }; // cylinder radius + std::vector radii { 25.f, 10.f, 5.f, 1.f }; + for( auto radius : radii ) { + + for( int i = 0; i <= latitudes; ++i ) { + + auto const latitude{ static_cast( M_PI * ( -0.5f + (float)( i ) / latitudes ) ) }; + auto const z{ std::sin( latitude ) }; + auto const zr{ std::cos( latitude ) }; + + for( int j = 0; j <= longitudes; ++j ) { + // NOTE: for the first and last row half of the points we create end up unused but, eh + auto const longitude{ static_cast( 2.0 * M_PI * (float)( j ) / longitudes ) }; + auto const x{ std::cos( longitude ) }; + auto const y{ std::sin( longitude ) }; + // NOTE: cartesian to opengl swap would be: -x, -z, -y + m_vertices.emplace_back( glm::vec3( -x * zr, -z * heightfactor, -y * zr ) * radius ); + // uvs + // NOTE: first and last row receives modified u values to deal with limitation of mapping onto triangles + auto u = ( + i == 0 ? longitude + longitudehalfstep : + i == latitudes ? longitude - longitudehalfstep : + longitude ); + m_uvs.emplace_back( + u / ( 2.0 * M_PI ) * radius, + 1.f - (float)( i ) / latitudes * radius * heightfactor * 0.5f / verticaltexturestretchfactor ); + + if( ( i == 0 ) || ( j == 0 ) ) { + // initial edge of the dome, don't start indices yet + ++index; + } + else { + // the end cones are built from one triangle of each quad, the middle rows use both + if( i < latitudes ) { + m_indices.emplace_back( index - 1 - ( longitudes + 1 ) ); + m_indices.emplace_back( index - 1 ); + m_indices.emplace_back( index ); + } + if( i > 1 ) { + m_indices.emplace_back( index ); + m_indices.emplace_back( index - ( longitudes + 1 ) ); + m_indices.emplace_back( index - 1 - ( longitudes + 1 ) ); + } + ++index; + } + } // longitude + } // latitude + } // radius +} + +void +opengl_precipitation::update() { + + // NOTE: we should really be checking state of each buffer as theoretically allocation could go wrong mid-way, but, eh + if( m_vertexbuffer == (GLuint)-1 ) { + + if( m_vertices.empty() ) { + // create visualization mesh + create( 18 ); + } + // cache entry state + ::glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT ); + + // build the buffers + ::glGenBuffers( 1, &m_vertexbuffer ); + ::glBindBuffer( GL_ARRAY_BUFFER, m_vertexbuffer ); + ::glBufferData( GL_ARRAY_BUFFER, m_vertices.size() * sizeof( glm::vec3 ), m_vertices.data(), GL_STATIC_DRAW ); + + ::glGenBuffers( 1, &m_uvbuffer ); + ::glBindBuffer( GL_ARRAY_BUFFER, m_uvbuffer ); + ::glBufferData( GL_ARRAY_BUFFER, m_uvs.size() * sizeof( glm::vec2 ), m_uvs.data(), GL_STATIC_DRAW ); + + ::glGenBuffers( 1, &m_indexbuffer ); + ::glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_indexbuffer ); + ::glBufferData( GL_ELEMENT_ARRAY_BUFFER, m_indices.size() * sizeof( unsigned short ), m_indices.data(), GL_STATIC_DRAW ); + // NOTE: vertex and index source data is superfluous past this point, but, eh + + // cleanup + ::glPopClientAttrib(); + ::glBindBuffer( GL_ARRAY_BUFFER, 0 ); + ::glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 ); + } + + // TODO: include weather type check in the entry conditions + if( m_overcast == Global.Overcast ) { return; } + + m_overcast = Global.Overcast; + + std::string const densitysuffix { ( + m_overcast < 1.35 ? + "_light" : + "_medium" ) }; + if( Global.Weather == "rain:" ) { + m_texture = GfxRenderer->Fetch_Texture( "fx/rain" + densitysuffix ); + } + else if( Global.Weather == "snow:" ) { + m_texture = GfxRenderer->Fetch_Texture( "fx/snow" + densitysuffix ); + } +} + +void +opengl_precipitation::render() { + + GfxRenderer->Bind_Texture( m_texture ); + + // cache entry state + ::glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT ); + + // positions + ::glBindBuffer( GL_ARRAY_BUFFER, m_vertexbuffer ); + ::glVertexPointer( 3, GL_FLOAT, sizeof( glm::vec3 ), reinterpret_cast( 0 ) ); + ::glEnableClientState( GL_VERTEX_ARRAY ); + // uvs + ::glBindBuffer( GL_ARRAY_BUFFER, m_uvbuffer ); + ::glClientActiveTexture( m_textureunit ); + ::glTexCoordPointer( 2, GL_FLOAT, sizeof( glm::vec2 ), reinterpret_cast( 0 ) ); + ::glEnableClientState( GL_TEXTURE_COORD_ARRAY ); + // uv transformation matrix + ::glMatrixMode( GL_TEXTURE ); + ::glLoadIdentity(); + ::glTranslatef( 0.f, simulation::Environment.precipitation().get_textureoffset(), 0.f ); + // indices + ::glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_indexbuffer ); + ::glDrawElements( GL_TRIANGLES, static_cast( m_indices.size() ), GL_UNSIGNED_SHORT, reinterpret_cast( 0 ) ); + // cleanup + ::glLoadIdentity(); + ::glMatrixMode( GL_MODELVIEW ); + ::glPopClientAttrib(); + ::glBindBuffer( GL_ARRAY_BUFFER, 0 ); + ::glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 ); +} diff --git a/openglprecipitation.h b/openglprecipitation.h new file mode 100644 index 00000000..2b5fb994 --- /dev/null +++ b/openglprecipitation.h @@ -0,0 +1,44 @@ +/* +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 "Texture.h" + +class opengl_precipitation { + +public: +// constructors + opengl_precipitation() = default; +// destructor + ~opengl_precipitation(); +// methods + inline + void + set_unit( GLint const Textureunit ) { + m_textureunit = Textureunit; } + void + update(); + void + render(); + +private: +// methods + void create( int const Tesselation ); +// members + std::vector m_vertices; + std::vector m_uvs; + std::vector m_indices; + GLuint m_vertexbuffer { (GLuint)-1 }; + GLuint m_uvbuffer { (GLuint)-1 }; + GLuint m_indexbuffer { (GLuint)-1 }; + GLint m_textureunit { 0 }; + texture_handle m_texture { -1 }; + float m_overcast { -1.f }; // cached overcast level, difference from current state triggers texture update +}; diff --git a/openglrenderer.cpp b/openglrenderer.cpp index 68badf83..5a6d9b61 100644 --- a/openglrenderer.cpp +++ b/openglrenderer.cpp @@ -51,7 +51,7 @@ opengl_renderer::Init( GLFWwindow *Window ) { std::vector{ m_normaltextureunit, m_diffusetextureunit } ); m_textures.assign_units( m_helpertextureunit, m_shadowtextureunit, m_normaltextureunit, m_diffusetextureunit ); // TODO: add reflections unit ui_layer::set_unit( m_diffusetextureunit ); - simulation::Environment.m_precipitation.set_unit( m_diffusetextureunit ); + m_precipitationrenderer.set_unit( m_diffusetextureunit ); select_unit( m_diffusetextureunit ); ::glDepthFunc( GL_LEQUAL ); @@ -548,7 +548,7 @@ opengl_renderer::Render_pass( rendermode const Mode ) { ::glPolygonOffset( 1.f, 1.f ); // main shadowmap - ::glBindFramebufferEXT( GL_FRAMEBUFFER, m_shadowframebuffer ); + ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_shadowframebuffer ); ::glViewport( 0, 0, m_shadowbuffersize, m_shadowbuffersize ); #ifdef EU07_USE_DEBUG_SHADOWMAP @@ -589,7 +589,7 @@ opengl_renderer::Render_pass( rendermode const Mode ) { ::glEnable( GL_POLYGON_OFFSET_FILL ); // alleviate depth-fighting ::glPolygonOffset( 1.f, 1.f ); - ::glBindFramebufferEXT( GL_FRAMEBUFFER, m_cabshadowframebuffer ); + ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_cabshadowframebuffer ); ::glViewport( 0, 0, m_shadowbuffersize / 2, m_shadowbuffersize / 2 ); #ifdef EU07_USE_DEBUG_CABSHADOWMAP @@ -719,10 +719,10 @@ opengl_renderer::Render_pass( rendermode const Mode ) { bool opengl_renderer::Render_reflections() { - if( Global.ReflectionUpdatesPerSecond == 0 ) { return false; } + if( Global.ReflectionUpdateInterval == 0 ) { return false; } - auto const timestamp { static_cast( Timer::GetTime() * 1000 ) }; - if( ( timestamp - m_environmentupdatetime < Global.ReflectionUpdatesPerSecond ) + auto const timestamp { Timer::GetTime() }; + if( ( timestamp - m_environmentupdatetime < Global.ReflectionUpdateInterval ) && ( glm::length( m_renderpass.camera.position() - m_environmentupdatelocation ) < 1000.0 ) ) { // run update every 5+ mins of simulation time, or at least 1km from the last location return false; @@ -736,7 +736,7 @@ opengl_renderer::Render_reflections() { m_environmentcubetextureface < GL_TEXTURE_CUBE_MAP_POSITIVE_X + 6; ++m_environmentcubetextureface ) { - ::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0, m_environmentcubetextureface, m_environmentcubetexture, 0 ); + ::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, m_environmentcubetextureface, m_environmentcubetexture, 0 ); Render_pass( rendermode::reflections ); } ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); @@ -1567,9 +1567,9 @@ opengl_renderer::Bind_Material( material_handle const Material ) { auto const &material = m_materials.material( Material ); if( false == Global.BasicRenderer ) { - m_textures.bind( textureunit::normals, material.texture2 ); + m_textures.bind( textureunit::normals, material.textures[1] ); } - m_textures.bind( textureunit::diffuse, material.texture1 ); + m_textures.bind( textureunit::diffuse, material.textures[0] ); } opengl_material const & @@ -1578,6 +1578,13 @@ opengl_renderer::Material( material_handle const Material ) const { return m_materials.material( Material ); } +// shader methods +std::shared_ptr +opengl_renderer::Fetch_Shader( std::string const &name ) { + + return std::shared_ptr(); +} + // texture methods void opengl_renderer::select_unit( GLint const Textureunit ) { @@ -1586,7 +1593,7 @@ opengl_renderer::select_unit( GLint const Textureunit ) { } texture_handle -opengl_renderer::Fetch_Texture( std::string const &Filename, bool const Loadnow ) { +opengl_renderer::Fetch_Texture( std::string const &Filename, bool const Loadnow, GLint format_hint ) { return m_textures.create( Filename, Loadnow ); } @@ -1597,12 +1604,36 @@ opengl_renderer::Bind_Texture( texture_handle const Texture ) { m_textures.bind( textureunit::diffuse, Texture ); } +void +opengl_renderer::Bind_Texture( std::size_t const Unit, texture_handle const Texture ) { + + m_textures.bind( Unit, Texture ); +} + +opengl_texture & +opengl_renderer::Texture( texture_handle const Texture ) { + + return m_textures.texture( Texture ); +} + opengl_texture const & opengl_renderer::Texture( texture_handle const Texture ) const { return m_textures.texture( Texture ); } +void +opengl_renderer::Pick_Control( std::function Callback ) { + + m_control_pick_requests.emplace_back( Callback ); +} + +void +opengl_renderer::Pick_Node( std::function Callback ) { + + m_node_pick_requests.emplace_back( Callback ); +} + void opengl_renderer::Render( scene::basic_region *Region ) { @@ -3012,7 +3043,7 @@ opengl_renderer::Render_precipitation() { // momentarily disable depth write, to allow vehicle cab drawn afterwards to mask it instead of leaving it 'inside' ::glDepthMask( GL_FALSE ); - simulation::Environment.m_precipitation.render(); + m_precipitationrenderer.render(); if( Global.bUseVBO ) { gfx::opengl_vbogeometrybank::reset(); } @@ -3646,7 +3677,7 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) { // utility methods -TSubModel * +void opengl_renderer::Update_Pick_Control() { #ifdef EU07_USE_PICKING_FRAMEBUFFER @@ -3690,10 +3721,14 @@ opengl_renderer::Update_Pick_Control() { } #endif m_pickcontrolitem = control; - return control; +// return control; + for( auto callback : m_control_pick_requests ) { + callback( m_pickcontrolitem ); + } + m_control_pick_requests.clear(); } -scene::basic_node * +void opengl_renderer::Update_Pick_Node() { #ifdef EU07_USE_PICKING_FRAMEBUFFER @@ -3739,7 +3774,11 @@ opengl_renderer::Update_Pick_Node() { } #endif m_picksceneryitem = node; - return node; +// return node; + for( auto callback : m_node_pick_requests ) { + callback( m_picksceneryitem ); + } + m_node_pick_requests.clear(); } // converts provided screen coordinates to world coordinates of most recent color pass @@ -3775,6 +3814,7 @@ opengl_renderer::Update( double const Deltatime ) { renderpass_config renderpass; setup_pass( renderpass, rendermode::color ); m_skydomerenderer.update(); + m_precipitationrenderer.update(); m_particlerenderer.update( renderpass.camera ); } @@ -3806,6 +3846,30 @@ opengl_renderer::Update( double const Deltatime ) { */ m_updateaccumulator += Deltatime; + if( ( true == Global.ControlPicking ) + && ( false == FreeFlyModeFlag ) ) { + if( ( false == m_control_pick_requests.empty() ) + || ( m_updateaccumulator >= 1.0 ) ) { + Update_Pick_Control(); + } + } + else { + m_pickcontrolitem = nullptr; + } + // temporary conditions for testing. eventually will be coupled with editor mode + if( ( true == Global.ControlPicking ) + && ( true == FreeFlyModeFlag ) + && ( ( true == DebugModeFlag ) + || ( true == EditorModeFlag ) ) ) { + if( ( false == m_control_pick_requests.empty() ) + || ( m_updateaccumulator >= 1.0 ) ) { + Update_Pick_Node(); + } + } + else { + m_picksceneryitem = nullptr; + } + if( m_updateaccumulator < 1.0 ) { // too early for any work return; @@ -3856,22 +3920,6 @@ opengl_renderer::Update( double const Deltatime ) { m_debugtimestext += m_textures.info(); } - if( ( true == Global.ControlPicking ) - && ( false == FreeFlyModeFlag ) ) { - Update_Pick_Control(); - } - else { - m_pickcontrolitem = nullptr; - } - // temporary conditions for testing. eventually will be coupled with editor mode - if( ( true == Global.ControlPicking ) - && ( true == DebugModeFlag ) - && ( true == FreeFlyModeFlag ) ) { - Update_Pick_Node(); - } - else { - m_picksceneryitem = nullptr; - } // dump last opengl error, if any auto const glerror = ::glGetError(); if( glerror != GL_NO_ERROR ) { @@ -3984,12 +4032,12 @@ opengl_renderer::Init_caps() { + " OpenGL Version: " + oglversion ); #ifdef EU07_USEIMGUIIMPLOPENGL2 - if( !GLEW_VERSION_1_5 ) { + if( !GLAD_GL_VERSION_1_5 ) { ErrorLog( "Requires openGL >= 1.5" ); return false; } #else - if( !GLEW_VERSION_3_0 ) { + if( !GLAD_GL_VERSION_3_0 ) { ErrorLog( "Requires openGL >= 3.0" ); return false; } @@ -3998,7 +4046,7 @@ opengl_renderer::Init_caps() { WriteLog( "Supported extensions: " + std::string((char *)glGetString( GL_EXTENSIONS )) ); WriteLog( std::string("Render path: ") + ( Global.bUseVBO ? "VBO" : "Display lists" ) ); - if( GLEW_EXT_framebuffer_object ) { + if( GL_EXT_framebuffer_object ) { m_framebuffersupport = true; WriteLog( "Framebuffer objects enabled" ); } diff --git a/openglrenderer.h b/openglrenderer.h index c080ed8e..523fd2b8 100644 --- a/openglrenderer.h +++ b/openglrenderer.h @@ -9,12 +9,12 @@ http://mozilla.org/MPL/2.0/. #pragma once -#include "GL/glew.h" #include "renderer.h" #include "opengllight.h" #include "openglcamera.h" #include "openglparticles.h" #include "openglskydome.h" +#include "openglprecipitation.h" #include "lightarray.h" #include "scene.h" #include "simulationenvironment.h" @@ -70,14 +70,24 @@ public: Bind_Material( material_handle const Material ) 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 ) override; + Fetch_Texture( std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA ) override; void - Bind_Texture( texture_handle const Texture ); + Bind_Texture( texture_handle const Texture ) override; + void + Bind_Texture( std::size_t const Unit, texture_handle const Texture ) override; + opengl_texture & + Texture( texture_handle const Texture ) override; opengl_texture const & Texture( texture_handle const Texture ) const override; // utility methods + void + Pick_Control( std::function Callback ) override; + void + Pick_Node( std::function Callback ) override; TSubModel const * Pick_Control() const override { return m_pickcontrolitem; } scene::basic_node const * @@ -87,9 +97,9 @@ public: // maintenance methods void Update( double const Deltatime ) override; - TSubModel * + void Update_Pick_Control() override; - scene::basic_node * + void Update_Pick_Node() override; glm::dvec3 Update_Mouse_Position() override; @@ -249,6 +259,7 @@ private: opengl_light m_sunlight; opengllight_array m_lights; opengl_skydome m_skydomerenderer; + opengl_precipitation m_precipitationrenderer; opengl_particles m_particlerenderer; // particle visualization subsystem /* float m_sunandviewangle; // cached dot product of sunlight and camera vectors @@ -288,7 +299,7 @@ private: GLuint m_environmentdepthbuffer { 0 }; bool m_environmentcubetexturesupport { false }; // indicates whether we can use the dynamic environment cube map int m_environmentcubetextureface { 0 }; // helper, currently processed cube map face - int m_environmentupdatetime { 0 }; // time of the most recent environment map update + double m_environmentupdatetime { 0.0 }; // time of the most recent environment map update glm::dvec3 m_environmentupdatelocation; // coordinates of most recent environment map update int m_helpertextureunit { GL_TEXTURE0 }; @@ -325,6 +336,8 @@ private: std::vector m_picksceneryitems; scene::basic_node *m_picksceneryitem { nullptr }; glm::vec3 m_worldmousecoordinates { 0.f }; + std::vector> m_control_pick_requests; + std::vector> m_node_pick_requests; #ifdef EU07_USE_DEBUG_CAMERA renderpass_config m_worldcamera; // debug item #endif diff --git a/openglskydome.cpp b/openglskydome.cpp index df190d59..1a7d124c 100644 --- a/openglskydome.cpp +++ b/openglskydome.cpp @@ -27,7 +27,7 @@ void opengl_skydome::update() { ::glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT ); // setup gpu data buffers: // ...static data... - if( m_indexbuffer == -1 ) { + if( m_indexbuffer == (GLuint)-1 ) { ::glGenBuffers( 1, &m_indexbuffer ); if( ( m_indexbuffer > 0 ) && ( m_indexbuffer != (GLuint)-1 ) ) { ::glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_indexbuffer ); @@ -36,7 +36,7 @@ void opengl_skydome::update() { m_indexcount = indices.size(); } } - if( m_vertexbuffer == -1 ) { + if( m_vertexbuffer == (GLuint)-1 ) { ::glGenBuffers( 1, &m_vertexbuffer ); if( ( m_vertexbuffer > 0 ) && ( m_vertexbuffer != (GLuint)-1 ) ) { ::glBindBuffer( GL_ARRAY_BUFFER, m_vertexbuffer ); @@ -45,7 +45,7 @@ void opengl_skydome::update() { } } // ...and dynamic data - if( m_coloursbuffer == -1 ) { + if( m_coloursbuffer == (GLuint)-1 ) { ::glGenBuffers( 1, &m_coloursbuffer ); if( ( m_coloursbuffer > 0 ) && ( m_coloursbuffer != (GLuint)-1 ) ) { ::glBindBuffer( GL_ARRAY_BUFFER, m_coloursbuffer ); diff --git a/openglskydome.h b/openglskydome.h index e72b06b1..90d88f89 100644 --- a/openglskydome.h +++ b/openglskydome.h @@ -9,8 +9,6 @@ http://mozilla.org/MPL/2.0/. #pragma once -#include "GL/glew.h" - class opengl_skydome { public: @@ -19,7 +17,7 @@ public: // destructor ~opengl_skydome(); // methods - // updates data stores on the opengl end. NOTE: unbinds + // updates data stores on the opengl end. NOTE: unbinds buffers void update(); // draws the skydome void render(); diff --git a/precipitation.cpp b/precipitation.cpp index 9db7abdb..d5968895 100644 --- a/precipitation.cpp +++ b/precipitation.cpp @@ -11,8 +11,6 @@ http://mozilla.org/MPL/2.0/. #include "precipitation.h" #include "Globals.h" -#include "openglmatrixstack.h" -#include "renderer.h" #include "Timer.h" #include "simulation.h" #include "Train.h" @@ -21,89 +19,11 @@ basic_precipitation::~basic_precipitation() { // TODO: release allocated resources } -void -basic_precipitation::create( int const Tesselation ) { - - auto const heightfactor { 10.f }; // height-to-radius factor - m_moverate *= heightfactor; - auto const verticaltexturestretchfactor { 1.5f }; // crude motion blur - - // create geometry chunk - auto const latitudes { 3 }; // just a cylinder with end cones - auto const longitudes { Tesselation }; - auto const longitudehalfstep { 0.5f * static_cast( 2.0 * M_PI * 1.f / longitudes ) }; // for crude uv correction - - std::uint16_t index = 0; - -// auto const radius { 25.f }; // cylinder radius - std::vector radii { 25.f, 10.f, 5.f, 1.f }; - for( auto radius : radii ) { - - for( int i = 0; i <= latitudes; ++i ) { - - auto const latitude{ static_cast( M_PI * ( -0.5f + (float)( i ) / latitudes ) ) }; - auto const z{ std::sin( latitude ) }; - auto const zr{ std::cos( latitude ) }; - - for( int j = 0; j <= longitudes; ++j ) { - // NOTE: for the first and last row half of the points we create end up unused but, eh - auto const longitude{ static_cast( 2.0 * M_PI * (float)( j ) / longitudes ) }; - auto const x{ std::cos( longitude ) }; - auto const y{ std::sin( longitude ) }; - // NOTE: cartesian to opengl swap would be: -x, -z, -y - m_vertices.emplace_back( glm::vec3( -x * zr, -z * heightfactor, -y * zr ) * radius ); - // uvs - // NOTE: first and last row receives modified u values to deal with limitation of mapping onto triangles - auto u = ( - i == 0 ? longitude + longitudehalfstep : - i == latitudes ? longitude - longitudehalfstep : - longitude ); - m_uvs.emplace_back( - u / ( 2.0 * M_PI ) * radius, - 1.f - (float)( i ) / latitudes * radius * heightfactor * 0.5f / verticaltexturestretchfactor ); - - if( ( i == 0 ) || ( j == 0 ) ) { - // initial edge of the dome, don't start indices yet - ++index; - } - else { - // the end cones are built from one triangle of each quad, the middle rows use both - if( i < latitudes ) { - m_indices.emplace_back( index - 1 - ( longitudes + 1 ) ); - m_indices.emplace_back( index - 1 ); - m_indices.emplace_back( index ); - } - if( i > 1 ) { - m_indices.emplace_back( index ); - m_indices.emplace_back( index - ( longitudes + 1 ) ); - m_indices.emplace_back( index - 1 - ( longitudes + 1 ) ); - } - ++index; - } - } // longitude - } // latitude - } // radius -} - bool basic_precipitation::init() { - create( 18 ); - - // TODO: select texture based on current overcast level - // TODO: when the overcast level dynamic change is in check the current level during render and pick the appropriate texture on the fly - std::string const densitysuffix { ( - Global.Overcast < 1.35 ? - "_light" : - "_medium" ) }; - if( Global.Weather == "rain:" ) { - m_moverateweathertypefactor = 2.f; - m_texture = GfxRenderer->Fetch_Texture( "fx/rain" + densitysuffix ); - } - else if( Global.Weather == "snow:" ) { - m_moverateweathertypefactor = 1.25f; - m_texture = GfxRenderer->Fetch_Texture( "fx/snow" + densitysuffix ); - } + auto const heightfactor { 10.f }; // height-to-radius factor + m_moverate *= heightfactor; return true; } @@ -160,49 +80,8 @@ basic_precipitation::update() { if( std::abs( m_cameramove.z ) < 0.001 ) { m_cameramove.z = 0.0; } } -void -basic_precipitation::render() { +float +basic_precipitation::get_textureoffset() const { - GfxRenderer->Bind_Texture( m_texture ); - - // cache entry state - ::glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT ); - - if( m_vertexbuffer == -1 ) { - // build the buffers - ::glGenBuffers( 1, &m_vertexbuffer ); - ::glBindBuffer( GL_ARRAY_BUFFER, m_vertexbuffer ); - ::glBufferData( GL_ARRAY_BUFFER, m_vertices.size() * sizeof( glm::vec3 ), m_vertices.data(), GL_STATIC_DRAW ); - - ::glGenBuffers( 1, &m_uvbuffer ); - ::glBindBuffer( GL_ARRAY_BUFFER, m_uvbuffer ); - ::glBufferData( GL_ARRAY_BUFFER, m_uvs.size() * sizeof( glm::vec2 ), m_uvs.data(), GL_STATIC_DRAW ); - - ::glGenBuffers( 1, &m_indexbuffer ); - ::glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_indexbuffer ); - ::glBufferData( GL_ELEMENT_ARRAY_BUFFER, m_indices.size() * sizeof( unsigned short ), m_indices.data(), GL_STATIC_DRAW ); - // NOTE: vertex and index source data is superfluous past this point, but, eh - } - // positions - ::glBindBuffer( GL_ARRAY_BUFFER, m_vertexbuffer ); - ::glVertexPointer( 3, GL_FLOAT, sizeof( glm::vec3 ), reinterpret_cast( 0 ) ); - ::glEnableClientState( GL_VERTEX_ARRAY ); - // uvs - ::glBindBuffer( GL_ARRAY_BUFFER, m_uvbuffer ); - ::glClientActiveTexture( m_textureunit ); - ::glTexCoordPointer( 2, GL_FLOAT, sizeof( glm::vec2 ), reinterpret_cast( 0 ) ); - ::glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - // uv transformation matrix - ::glMatrixMode( GL_TEXTURE ); - ::glLoadIdentity(); - ::glTranslatef( 0.f, m_textureoffset, 0.f ); - // indices - ::glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_indexbuffer ); - ::glDrawElements( GL_TRIANGLES, static_cast( m_indices.size() ), GL_UNSIGNED_SHORT, reinterpret_cast( 0 ) ); - // cleanup - ::glLoadIdentity(); - ::glMatrixMode( GL_MODELVIEW ); - ::glPopClientAttrib(); - ::glBindBuffer( GL_ARRAY_BUFFER, 0 ); - ::glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 ); + return m_textureoffset; } diff --git a/precipitation.h b/precipitation.h index 5ebebef7..2926fd1b 100644 --- a/precipitation.h +++ b/precipitation.h @@ -9,8 +9,6 @@ http://mozilla.org/MPL/2.0/. #pragma once -#include "Texture.h" - // based on "Rendering Falling Rain and Snow" // by Niniane Wang, Bretton Wade @@ -22,32 +20,17 @@ public: // destructor ~basic_precipitation(); // methods - inline - void - set_unit( GLint const Textureunit ) { - m_textureunit = Textureunit; } bool init(); void update(); - void - render(); + float + get_textureoffset() const; glm::dvec3 m_cameramove{ 0.0 }; private: -// methods - void create( int const Tesselation ); - // members - std::vector m_vertices; - std::vector m_uvs; - std::vector m_indices; - GLuint m_vertexbuffer { (GLuint)-1 }; - GLuint m_uvbuffer { (GLuint)-1 }; - GLuint m_indexbuffer { (GLuint)-1 }; - GLint m_textureunit { 0 }; - texture_handle m_texture { -1 }; float m_textureoffset { 0.f }; float m_moverate { 30 * 0.001f }; float m_moverateweathertypefactor { 1.f }; // medium-dependent; 1.0 for snow, faster for rain diff --git a/ref/imgui/examples/imgui_impl_opengl2.cpp b/ref/imgui/examples/imgui_impl_opengl2.cpp index 8bb7b798..5204eb78 100644 --- a/ref/imgui/examples/imgui_impl_opengl2.cpp +++ b/ref/imgui/examples/imgui_impl_opengl2.cpp @@ -27,7 +27,7 @@ // 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. #include "stdafx.h" -#define IMGUI_IMPL_OPENGL_LOADER_GLEW +#define IMGUI_IMPL_OPENGL_LOADER_GLAD #include "imgui.h" #include "imgui_impl_opengl2.h" diff --git a/ref/imgui/examples/imgui_impl_opengl3.cpp b/ref/imgui/examples/imgui_impl_opengl3.cpp index e901d1eb..534ac796 100644 --- a/ref/imgui/examples/imgui_impl_opengl3.cpp +++ b/ref/imgui/examples/imgui_impl_opengl3.cpp @@ -48,7 +48,7 @@ //---------------------------------------- #include "stdafx.h" -#define IMGUI_IMPL_OPENGL_LOADER_GLEW +#define IMGUI_IMPL_OPENGL_LOADER_GLAD #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS diff --git a/renderer.h b/renderer.h index 052a4e4f..0daaece1 100644 --- a/renderer.h +++ b/renderer.h @@ -40,18 +40,25 @@ public: 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 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; // texture methods - virtual auto Fetch_Texture( std::string const &Filename, bool const Loadnow = true ) -> texture_handle = 0; + virtual auto Fetch_Texture( std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA ) -> texture_handle = 0; virtual void Bind_Texture( texture_handle const Texture ) = 0; + virtual void Bind_Texture( std::size_t const Unit, texture_handle const Texture ) = 0; + 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 auto Pick_Control() const -> TSubModel const * = 0; virtual auto Pick_Node() const -> scene::basic_node const * = 0; + virtual auto Mouse_Position() const -> glm::dvec3 = 0; // maintenance methods virtual void Update( double const Deltatime ) = 0; - virtual auto Update_Pick_Control() -> TSubModel * = 0; - virtual auto Update_Pick_Node() -> scene::basic_node * = 0; + virtual void Update_Pick_Control() = 0; + virtual void Update_Pick_Node() = 0; virtual auto Update_Mouse_Position() -> glm::dvec3 = 0; // debug methods virtual auto info_times() const -> std::string const & = 0; diff --git a/scene.h b/scene.h index c3430ee8..916dd916 100644 --- a/scene.h +++ b/scene.h @@ -22,6 +22,9 @@ http://mozilla.org/MPL/2.0/. #include "Traction.h" #include "sound.h" +class opengl_renderer; +class opengl33_renderer; + namespace scene { int const EU07_CELLSIZE = 250; @@ -63,6 +66,7 @@ struct scratch_data { class basic_cell { friend opengl_renderer; + friend opengl33_renderer; public: // constructors @@ -200,6 +204,7 @@ private: class basic_section { friend opengl_renderer; + friend opengl33_renderer; public: // constructors @@ -304,6 +309,7 @@ private: class basic_region { friend opengl_renderer; + friend opengl33_renderer; public: // constructors diff --git a/scenenode.cpp b/scenenode.cpp index d0a0abea..9198af74 100644 --- a/scenenode.cpp +++ b/scenenode.cpp @@ -200,7 +200,7 @@ shape_node::import( cParser &Input, scene::node_data const &Nodedata ) { // TBT, TODO: add methods to material manager to access these simpler auto const texturehandle = ( m_data.material != null_handle ? - GfxRenderer->Material( m_data.material ).texture1 : + GfxRenderer->Material( m_data.material ).textures[0] : null_handle ); auto const &texture = ( texturehandle ? @@ -346,7 +346,7 @@ shape_node::convert( TSubModel const *Submodel ) { m_data.lighting.diffuse = Submodel->f4Diffuse; m_data.lighting.specular = Submodel->f4Specular; m_data.material = Submodel->m_material; - m_data.translucent = ( true == GfxRenderer->Material( m_data.material ).has_alpha ); + m_data.translucent = ( true == GfxRenderer->Material( m_data.material ).is_translucent() ); // NOTE: we set unlimited view range typical for terrain, because we don't expect to convert any other 3d models m_data.rangesquared_max = std::numeric_limits::max(); diff --git a/simulationenvironment.h b/simulationenvironment.h index 5944d613..009a4a3b 100644 --- a/simulationenvironment.h +++ b/simulationenvironment.h @@ -18,11 +18,13 @@ http://mozilla.org/MPL/2.0/. #include "sound.h" class opengl_renderer; +class opengl33_renderer; // wrapper for environment elements -- sky, sun, stars, clouds etc class world_environment { friend opengl_renderer; + friend opengl33_renderer; public: // methods @@ -43,6 +45,9 @@ public: inline auto & skydome() { return m_skydome; } + inline auto const & + precipitation() const { + return m_precipitation; } inline auto const & wind() const { return m_wind.vector; } diff --git a/sky.h b/sky.h index 9397422a..2df0a143 100644 --- a/sky.h +++ b/sky.h @@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/. class TSky { friend opengl_renderer; + friend opengl33_renderer; public: TSky() = default; diff --git a/stars.h b/stars.h index 9ca380ed..bae1ada6 100644 --- a/stars.h +++ b/stars.h @@ -9,6 +9,7 @@ class cStars { friend opengl_renderer; + friend opengl33_renderer; public: // types: diff --git a/stdafx.h b/stdafx.h index b484fd2d..d289f76c 100644 --- a/stdafx.h +++ b/stdafx.h @@ -66,31 +66,27 @@ #include #include #include +#include +#include +#include +#include #ifdef NDEBUG #define EU07_BUILD_STATIC #endif -#ifdef EU07_BUILD_STATIC -#define GLEW_STATIC -#else -#ifdef _WIN32 -#define GLFW_DLL -#endif // _windows -#endif // build_static -#ifndef __ANDROID__ -#include "GL/glew.h" -#else -#include -#include -#endif -#ifdef _WIN32 -#include "GL/wglew.h" -#endif -#define GLFW_INCLUDE_GLU -//m7todo: jest tu bo nie chciao mi si wpycha do wszystkich plikw -#ifndef __ANDROID__ +#include "glad/glad.h" + +#include "GL/glu.h" + +#define GLFW_INCLUDE_NONE #include + +#ifndef GLFW_TRUE +#define GLFW_FALSE 0 +#define GLFW_TRUE 1 +#define glfwGetKeyName(a, b) ("") +#define glfwFocusWindow(w) #endif #define GLM_ENABLE_EXPERIMENTAL @@ -102,10 +98,14 @@ #include #include #include +#include int const null_handle = 0; #include "openglmatrixstack.h" +#define STRINGIZE_DETAIL(x) #x +#define STRINGIZE(x) STRINGIZE_DETAIL(x) +#define glDebug(x) if (GLAD_GL_GREMEDY_string_marker) glStringMarkerGREMEDY(0, __FILE__ ":" STRINGIZE(__LINE__) ": " x); #include "openglcolor.h" // imgui.h comes with its own operator new which gets wrecked by dbg_new, so we temporarily disable the latter diff --git a/sun.h b/sun.h index babc14af..71c11094 100644 --- a/sun.h +++ b/sun.h @@ -1,8 +1,6 @@ #pragma once #include "windows.h" -#include "GL/glew.h" -#include "GL/wglew.h" ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/utilities.cpp b/utilities.cpp index 5393d28e..620fb24f 100644 --- a/utilities.cpp +++ b/utilities.cpp @@ -110,6 +110,12 @@ double Random(double a, double b) return dis(Global.random_engine); } +double LocalRandom(double a, double b) +{ + uint32_t val = Global.local_random_engine(); + return interpolate(a, b, (double)val / Global.random_engine.max()); +} + bool FuzzyLogic(double Test, double Threshold, double Probability) { if ((Test > Threshold) && (!DebugModeFlag)) diff --git a/utilities.h b/utilities.h index 85734f37..8b7f99c5 100644 --- a/utilities.h +++ b/utilities.h @@ -56,6 +56,7 @@ inline long Round(double const f) } double Random(double a, double b); +double LocalRandom( double a, double b ); inline double Random() { @@ -67,6 +68,16 @@ inline double Random(double b) return Random(0.0, b); } +inline double LocalRandom() +{ + return LocalRandom( 0.0, 1.0 ); +} + +inline double LocalRandom( double b ) +{ + return LocalRandom( 0.0, b ); +} + inline double BorlandTime() { auto timesinceepoch = std::time( nullptr ); diff --git a/version.h b/version.h index 4813329f..8e06acc3 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 19 -#define VERSION_MINOR 1014 +#define VERSION_MINOR 1026 #define VERSION_REVISION 0