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