Index: ps/trunk/source/renderer/Renderer.cpp =================================================================== --- ps/trunk/source/renderer/Renderer.cpp (revision 27183) +++ ps/trunk/source/renderer/Renderer.cpp (revision 27184) @@ -1,793 +1,796 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Renderer.h" #include "graphics/Canvas2D.h" #include "graphics/CinemaManager.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/ModelDef.h" #include "graphics/TerrainTextureManager.h" #include "i18n/L10n.h" #include "lib/allocators/shared_ptr.h" #include "lib/tex/tex.h" #include "gui/GUIManager.h" #include "ps/CConsole.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/CStrInternStatic.h" #include "ps/Game.h" #include "ps/GameSetup/Config.h" #include "ps/GameSetup/GameSetup.h" #include "ps/Globals.h" #include "ps/Loader.h" #include "ps/Profile.h" #include "ps/Filesystem.h" #include "ps/World.h" #include "ps/ProfileViewer.h" #include "graphics/Camera.h" #include "graphics/FontManager.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "graphics/Texture.h" #include "graphics/TextureManager.h" #include "ps/Util.h" #include "ps/VideoMode.h" #include "renderer/backend/IDevice.h" #include "renderer/DebugRenderer.h" #include "renderer/PostprocManager.h" #include "renderer/RenderingOptions.h" #include "renderer/RenderModifiers.h" #include "renderer/SceneRenderer.h" #include "renderer/TimeManager.h" #include "renderer/VertexBufferManager.h" #include "tools/atlas/GameInterface/GameLoop.h" #include "tools/atlas/GameInterface/View.h" #include namespace { size_t g_NextScreenShotNumber = 0; /////////////////////////////////////////////////////////////////////////////////// // CRendererStatsTable - Profile display of rendering stats /** * Class CRendererStatsTable: Implementation of AbstractProfileTable to * display the renderer stats in-game. * * Accesses CRenderer::m_Stats by keeping the reference passed to the * constructor. */ class CRendererStatsTable : public AbstractProfileTable { NONCOPYABLE(CRendererStatsTable); public: CRendererStatsTable(const CRenderer::Stats& st); // Implementation of AbstractProfileTable interface CStr GetName(); CStr GetTitle(); size_t GetNumberRows(); const std::vector& GetColumns(); CStr GetCellText(size_t row, size_t col); AbstractProfileTable* GetChild(size_t row); private: /// Reference to the renderer singleton's stats const CRenderer::Stats& Stats; /// Column descriptions std::vector columnDescriptions; enum { Row_DrawCalls = 0, Row_TerrainTris, Row_WaterTris, Row_ModelTris, Row_OverlayTris, Row_BlendSplats, Row_Particles, Row_VBReserved, Row_VBAllocated, Row_TextureMemory, Row_ShadersLoaded, // Must be last to count number of rows NumberRows }; }; // Construction CRendererStatsTable::CRendererStatsTable(const CRenderer::Stats& st) : Stats(st) { columnDescriptions.push_back(ProfileColumn("Name", 230)); columnDescriptions.push_back(ProfileColumn("Value", 100)); } // Implementation of AbstractProfileTable interface CStr CRendererStatsTable::GetName() { return "renderer"; } CStr CRendererStatsTable::GetTitle() { return "Renderer statistics"; } size_t CRendererStatsTable::GetNumberRows() { return NumberRows; } const std::vector& CRendererStatsTable::GetColumns() { return columnDescriptions; } CStr CRendererStatsTable::GetCellText(size_t row, size_t col) { char buf[256]; switch(row) { case Row_DrawCalls: if (col == 0) return "# draw calls"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_DrawCalls); return buf; case Row_TerrainTris: if (col == 0) return "# terrain tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_TerrainTris); return buf; case Row_WaterTris: if (col == 0) return "# water tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_WaterTris); return buf; case Row_ModelTris: if (col == 0) return "# model tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_ModelTris); return buf; case Row_OverlayTris: if (col == 0) return "# overlay tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_OverlayTris); return buf; case Row_BlendSplats: if (col == 0) return "# blend splats"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_BlendSplats); return buf; case Row_Particles: if (col == 0) return "# particles"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_Particles); return buf; case Row_VBReserved: if (col == 0) return "VB reserved"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesReserved() / 1024); return buf; case Row_VBAllocated: if (col == 0) return "VB allocated"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesAllocated() / 1024); return buf; case Row_TextureMemory: if (col == 0) return "textures uploaded"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_Renderer.GetTextureManager().GetBytesUploaded() / 1024); return buf; case Row_ShadersLoaded: if (col == 0) return "shader effects loaded"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_Renderer.GetShaderManager().GetNumEffectsLoaded()); return buf; default: return "???"; } } AbstractProfileTable* CRendererStatsTable::GetChild(size_t UNUSED(row)) { return 0; } } // anonymous namespace /////////////////////////////////////////////////////////////////////////////////// // CRenderer implementation /** * Struct CRendererInternals: Truly hide data that is supposed to be hidden * in this structure so it won't even appear in header files. */ class CRenderer::Internals { NONCOPYABLE(Internals); public: std::unique_ptr deviceCommandContext; /// true if CRenderer::Open has been called bool IsOpen; /// true if shaders need to be reloaded bool ShadersDirty; /// Table to display renderer stats in-game via profile system CRendererStatsTable profileTable; /// Shader manager CShaderManager shaderManager; /// Texture manager CTextureManager textureManager; /// Time manager CTimeManager timeManager; /// Postprocessing effect manager CPostprocManager postprocManager; CSceneRenderer sceneRenderer; CDebugRenderer debugRenderer; CFontManager fontManager; Internals() : IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats), deviceCommandContext(g_VideoMode.GetBackendDevice()->CreateCommandContext()), textureManager(g_VFS, false, g_VideoMode.GetBackendDevice()) { } }; CRenderer::CRenderer() { TIMER(L"InitRenderer"); m = std::make_unique(); g_ProfileViewer.AddRootTable(&m->profileTable); m_Width = 0; m_Height = 0; m_Stats.Reset(); // Create terrain related stuff. new CTerrainTextureManager; Open(g_xres, g_yres); // Setup lighting environment. Since the Renderer accesses the // lighting environment through a pointer, this has to be done before // the first Frame. GetSceneRenderer().SetLightEnv(&g_LightEnv); // I haven't seen the camera affecting GUI rendering and such, but the // viewport has to be updated according to the video mode SViewPort vp; vp.m_X = 0; vp.m_Y = 0; vp.m_Width = g_xres; vp.m_Height = g_yres; SetViewport(vp); ModelDefActivateFastImpl(); ColorActivateFastImpl(); ModelRenderer::Init(); } CRenderer::~CRenderer() { delete &g_TexMan; // We no longer UnloadWaterTextures here - // that is the responsibility of the module that asked for // them to be loaded (i.e. CGameView). m.reset(); } void CRenderer::ReloadShaders() { ENSURE(m->IsOpen); m->sceneRenderer.ReloadShaders(); m->ShadersDirty = false; } bool CRenderer::Open(int width, int height) { m->IsOpen = true; // Dimensions m_Width = width; m_Height = height; // Validate the currently selected render path SetRenderPath(g_RenderingOptions.GetRenderPath()); if (m->postprocManager.IsEnabled()) m->postprocManager.Initialize(); m->sceneRenderer.Initialize(); return true; } void CRenderer::Resize(int width, int height) { m_Width = width; m_Height = height; m->postprocManager.Resize(); m->sceneRenderer.Resize(width, height); } void CRenderer::SetRenderPath(RenderPath rp) { if (!m->IsOpen) { // Delay until Open() is called. return; } // Renderer has been opened, so validate the selected renderpath const bool hasShadersSupport = g_VideoMode.GetBackendDevice()->GetCapabilities().ARBShaders || g_VideoMode.GetBackendDevice()->GetBackend() != Renderer::Backend::Backend::GL_ARB; if (rp == RenderPath::DEFAULT) { if (hasShadersSupport) rp = RenderPath::SHADER; else rp = RenderPath::FIXED; } if (rp == RenderPath::SHADER) { if (!hasShadersSupport) { LOGWARNING("Falling back to fixed function\n"); rp = RenderPath::FIXED; } } // TODO: remove this once capabilities have been properly extracted and the above checks have been moved elsewhere. g_RenderingOptions.m_RenderPath = rp; MakeShadersDirty(); } bool CRenderer::ShouldRender() const { return !g_app_minimized && (g_app_has_focus || !g_VideoMode.IsInFullscreen()); } void CRenderer::RenderFrame(const bool needsPresent) { // Do not render if not focused while in fullscreen or minimised, // as that triggers a difficult-to-reproduce crash on some graphic cards. if (!ShouldRender()) return; if (m_ScreenShotType == ScreenShotType::BIG) { RenderBigScreenShot(needsPresent); } else if (m_ScreenShotType == ScreenShotType::DEFAULT) { RenderScreenShot(needsPresent); } else { if (needsPresent) - g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer(); + { + // In case of no acquired backbuffer we have nothing render to. + if (!g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer()) + return; + } if (m_ShouldPreloadResourcesBeforeNextFrame) { m_ShouldPreloadResourcesBeforeNextFrame = false; // We don't need to render logger for the preload. RenderFrameImpl(true, false); } RenderFrameImpl(true, true); m->deviceCommandContext->Flush(); if (needsPresent) g_VideoMode.GetBackendDevice()->Present(); } } void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger) { PROFILE3("render"); g_Profiler2.RecordGPUFrameStart(); g_TexMan.UploadResourcesIfNeeded(m->deviceCommandContext.get()); m->textureManager.MakeUploadProgress(m->deviceCommandContext.get()); // prepare before starting the renderer frame if (g_Game && g_Game->IsGameStarted()) g_Game->GetView()->BeginFrame(); if (g_Game) m->sceneRenderer.SetSimulation(g_Game->GetSimulation2()); // start new frame BeginFrame(); if (g_Game && g_Game->IsGameStarted()) { g_Game->GetView()->Render(); } m->deviceCommandContext->BeginFramebufferPass( m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer()); // If we're in Atlas game view, render special tools if (g_AtlasGameLoop && g_AtlasGameLoop->view) { g_AtlasGameLoop->view->DrawCinemaPathTool(); } if (g_Game && g_Game->IsGameStarted()) { g_Game->GetView()->GetCinema()->Render(); } RenderFrame2D(renderGUI, renderLogger); m->deviceCommandContext->EndFramebufferPass(); EndFrame(); const Stats& stats = GetStats(); PROFILE2_ATTR("draw calls: %zu", stats.m_DrawCalls); PROFILE2_ATTR("terrain tris: %zu", stats.m_TerrainTris); PROFILE2_ATTR("water tris: %zu", stats.m_WaterTris); PROFILE2_ATTR("model tris: %zu", stats.m_ModelTris); PROFILE2_ATTR("overlay tris: %zu", stats.m_OverlayTris); PROFILE2_ATTR("blend splats: %zu", stats.m_BlendSplats); PROFILE2_ATTR("particles: %zu", stats.m_Particles); g_Profiler2.RecordGPUFrameEnd(); } void CRenderer::RenderFrame2D(const bool renderGUI, const bool renderLogger) { CCanvas2D canvas(g_xres, g_yres, g_VideoMode.GetScale(), m->deviceCommandContext.get()); m->sceneRenderer.RenderTextOverlays(canvas); if (renderGUI) { GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render GUI"); // All GUI elements are drawn in Z order to render semi-transparent // objects correctly. g_GUI->Draw(canvas); } // If we're in Atlas game view, render special overlays (e.g. editor bandbox). if (g_AtlasGameLoop && g_AtlasGameLoop->view) { g_AtlasGameLoop->view->DrawOverlays(canvas); } { GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render console"); g_Console->Render(canvas); } if (renderLogger) { GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render logger"); g_Logger->Render(canvas); } { GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render profiler"); // Profile information g_ProfileViewer.RenderProfile(canvas); } } void CRenderer::RenderScreenShot(const bool needsPresent) { m_ScreenShotType = ScreenShotType::NONE; // get next available numbered filename // note: %04d -> always 4 digits, so sorting by filename works correctly. const VfsPath filenameFormat(L"screenshots/screenshot%04d.png"); VfsPath filename; vfs::NextNumberedFilename(g_VFS, filenameFormat, g_NextScreenShotNumber, filename); const size_t width = static_cast(g_xres), height = static_cast(g_yres); const size_t bpp = 24; - if (needsPresent) - g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer(); + if (needsPresent && !g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer()) + return; // Hide log messages and re-render RenderFrameImpl(true, false); const size_t img_size = width * height * bpp / 8; const size_t hdr_size = tex_hdr_size(filename); std::shared_ptr buf; AllocateAligned(buf, hdr_size + img_size, maxSectorSize); void* img = buf.get() + hdr_size; Tex t; if (t.wrap(width, height, bpp, TEX_BOTTOM_UP, buf, hdr_size) < 0) return; m->deviceCommandContext->ReadbackFramebufferSync(0, 0, width, height, img); m->deviceCommandContext->Flush(); if (needsPresent) g_VideoMode.GetBackendDevice()->Present(); if (tex_write(&t, filename) == INFO::OK) { OsPath realPath; g_VFS->GetRealPath(filename, realPath); LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8()); debug_printf( CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(), realPath.string8().c_str()); } else LOGERROR("Error writing screenshot to '%s'", filename.string8()); } void CRenderer::RenderBigScreenShot(const bool needsPresent) { m_ScreenShotType = ScreenShotType::NONE; // If the game hasn't started yet then use WriteScreenshot to generate the image. if (!g_Game) return RenderScreenShot(needsPresent); int tiles = 4, tileWidth = 256, tileHeight = 256; CFG_GET_VAL("screenshot.tiles", tiles); CFG_GET_VAL("screenshot.tilewidth", tileWidth); CFG_GET_VAL("screenshot.tileheight", tileHeight); if (tiles <= 0 || tileWidth <= 0 || tileHeight <= 0 || tileWidth * tiles % 4 != 0 || tileHeight * tiles % 4 != 0) { LOGWARNING("Invalid big screenshot size: tiles=%d tileWidth=%d tileHeight=%d", tiles, tileWidth, tileHeight); return; } // get next available numbered filename // note: %04d -> always 4 digits, so sorting by filename works correctly. const VfsPath filenameFormat(L"screenshots/screenshot%04d.bmp"); VfsPath filename; vfs::NextNumberedFilename(g_VFS, filenameFormat, g_NextScreenShotNumber, filename); // Slightly ugly and inflexible: Always draw 640*480 tiles onto the screen, and // hope the screen is actually large enough for that. ENSURE(g_xres >= tileWidth && g_yres >= tileHeight); const int imageWidth = tileWidth * tiles, imageHeight = tileHeight * tiles; const int bpp = 24; const size_t imageSize = imageWidth * imageHeight * bpp / 8; const size_t tileSize = tileWidth * tileHeight * bpp / 8; const size_t headerSize = tex_hdr_size(filename); void* tileData = malloc(tileSize); if (!tileData) { WARN_IF_ERR(ERR::NO_MEM); return; } std::shared_ptr imageBuffer; AllocateAligned(imageBuffer, headerSize + imageSize, maxSectorSize); Tex t; void* img = imageBuffer.get() + headerSize; if (t.wrap(imageWidth, imageHeight, bpp, TEX_BOTTOM_UP, imageBuffer, headerSize) < 0) { free(tileData); return; } CCamera oldCamera = *g_Game->GetView()->GetCamera(); // Resize various things so that the sizes and aspect ratios are correct { g_Renderer.Resize(tileWidth, tileHeight); SViewPort vp = { 0, 0, tileWidth, tileHeight }; g_Game->GetView()->SetViewport(vp); } // Render each tile CMatrix3D projection; projection.SetIdentity(); const float aspectRatio = 1.0f * tileWidth / tileHeight; for (int tileY = 0; tileY < tiles; ++tileY) { for (int tileX = 0; tileX < tiles; ++tileX) { // Adjust the camera to render the appropriate region if (oldCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE) { projection.SetPerspectiveTile(oldCamera.GetFOV(), aspectRatio, oldCamera.GetNearPlane(), oldCamera.GetFarPlane(), tiles, tileX, tileY); } g_Game->GetView()->GetCamera()->SetProjection(projection); - if (needsPresent) - g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer(); - - RenderFrameImpl(false, false); + if (needsPresent && g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer()) + { + RenderFrameImpl(false, false); - m->deviceCommandContext->ReadbackFramebufferSync(0, 0, tileWidth, tileHeight, tileData); - m->deviceCommandContext->Flush(); - if (needsPresent) + m->deviceCommandContext->ReadbackFramebufferSync(0, 0, tileWidth, tileHeight, tileData); + m->deviceCommandContext->Flush(); g_VideoMode.GetBackendDevice()->Present(); + } // Copy the tile pixels into the main image for (int y = 0; y < tileHeight; ++y) { void* dest = static_cast(img) + ((tileY * tileHeight + y) * imageWidth + (tileX * tileWidth)) * bpp / 8; void* src = static_cast(tileData) + y * tileWidth * bpp / 8; memcpy(dest, src, tileWidth * bpp / 8); } } } // Restore the viewport settings { g_Renderer.Resize(g_xres, g_yres); SViewPort vp = { 0, 0, g_xres, g_yres }; g_Game->GetView()->SetViewport(vp); g_Game->GetView()->GetCamera()->SetProjectionFromCamera(oldCamera); } if (tex_write(&t, filename) == INFO::OK) { OsPath realPath; g_VFS->GetRealPath(filename, realPath); LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8()); debug_printf( CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(), realPath.string8().c_str()); } else LOGERROR("Error writing screenshot to '%s'", filename.string8()); free(tileData); } void CRenderer::BeginFrame() { PROFILE("begin frame"); // Zero out all the per-frame stats. m_Stats.Reset(); if (m->ShadersDirty) ReloadShaders(); m->sceneRenderer.BeginFrame(); } void CRenderer::EndFrame() { PROFILE3("end frame"); m->sceneRenderer.EndFrame(); } void CRenderer::SetViewport(const SViewPort &vp) { m_Viewport = vp; Renderer::Backend::IDeviceCommandContext::Rect viewportRect; viewportRect.x = vp.m_X; viewportRect.y = vp.m_Y; viewportRect.width = vp.m_Width; viewportRect.height = vp.m_Height; m->deviceCommandContext->SetViewports(1, &viewportRect); } SViewPort CRenderer::GetViewport() { return m_Viewport; } void CRenderer::MakeShadersDirty() { m->ShadersDirty = true; m->sceneRenderer.MakeShadersDirty(); } CTextureManager& CRenderer::GetTextureManager() { return m->textureManager; } CShaderManager& CRenderer::GetShaderManager() { return m->shaderManager; } CTimeManager& CRenderer::GetTimeManager() { return m->timeManager; } CPostprocManager& CRenderer::GetPostprocManager() { return m->postprocManager; } CSceneRenderer& CRenderer::GetSceneRenderer() { return m->sceneRenderer; } CDebugRenderer& CRenderer::GetDebugRenderer() { return m->debugRenderer; } CFontManager& CRenderer::GetFontManager() { return m->fontManager; } void CRenderer::PreloadResourcesBeforeNextFrame() { m_ShouldPreloadResourcesBeforeNextFrame = true; } void CRenderer::MakeScreenShotOnNextFrame(ScreenShotType screenShotType) { m_ScreenShotType = screenShotType; } Renderer::Backend::IDeviceCommandContext* CRenderer::GetDeviceCommandContext() { return m->deviceCommandContext.get(); } Index: ps/trunk/source/renderer/backend/IDevice.h =================================================================== --- ps/trunk/source/renderer/backend/IDevice.h (revision 27183) +++ ps/trunk/source/renderer/backend/IDevice.h (revision 27184) @@ -1,117 +1,117 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_RENDERER_BACKEND_IDEVICE #define INCLUDED_RENDERER_BACKEND_IDEVICE #include "graphics/Color.h" #include "renderer/backend/Backend.h" #include "renderer/backend/Format.h" #include "renderer/backend/IBuffer.h" #include "renderer/backend/IDevice.h" #include "renderer/backend/IDeviceCommandContext.h" #include "renderer/backend/IFramebuffer.h" #include "renderer/backend/IShaderProgram.h" #include "renderer/backend/ITexture.h" #include "scriptinterface/ScriptForward.h" #include #include #include class CShaderDefines; class CStr; namespace Renderer { namespace Backend { class IDevice { public: struct Capabilities { bool S3TC; bool ARBShaders; bool ARBShadersShadow; bool computeShaders; bool debugLabels; bool debugScopedLabels; bool multisampling; bool anisotropicFiltering; uint32_t maxSampleCount; float maxAnisotropy; uint32_t maxTextureSize; bool instancing; }; virtual ~IDevice() {} virtual Backend GetBackend() const = 0; virtual const std::string& GetName() const = 0; virtual const std::string& GetVersion() const = 0; virtual const std::string& GetDriverInformation() const = 0; virtual const std::vector& GetExtensions() const = 0; virtual void Report(const ScriptRequest& rq, JS::HandleValue settings) = 0; virtual IFramebuffer* GetCurrentBackbuffer() = 0; virtual std::unique_ptr CreateCommandContext() = 0; virtual std::unique_ptr CreateTexture( const char* name, const ITexture::Type type, const uint32_t usage, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) = 0; virtual std::unique_ptr CreateTexture2D( const char* name, const uint32_t usage, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount = 1, const uint32_t sampleCount = 1) = 0; virtual std::unique_ptr CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment) = 0; virtual std::unique_ptr CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment, const CColor& clearColor) = 0; virtual std::unique_ptr CreateBuffer( const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) = 0; virtual std::unique_ptr CreateShaderProgram( const CStr& name, const CShaderDefines& defines) = 0; - virtual void AcquireNextBackbuffer() = 0; + virtual bool AcquireNextBackbuffer() = 0; virtual void Present() = 0; virtual bool IsTextureFormatSupported(const Format format) const = 0; virtual bool IsFramebufferFormatSupported(const Format format) const = 0; virtual const Capabilities& GetCapabilities() const = 0; }; } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_BACKEND_IDEVICE Index: ps/trunk/source/renderer/backend/dummy/Device.cpp =================================================================== --- ps/trunk/source/renderer/backend/dummy/Device.cpp (revision 27183) +++ ps/trunk/source/renderer/backend/dummy/Device.cpp (revision 27184) @@ -1,146 +1,147 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Device.h" #include "renderer/backend/dummy/Buffer.h" #include "renderer/backend/dummy/DeviceCommandContext.h" #include "renderer/backend/dummy/Framebuffer.h" #include "renderer/backend/dummy/ShaderProgram.h" #include "renderer/backend/dummy/Texture.h" #include "scriptinterface/JSON.h" #include "scriptinterface/Object.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptRequest.h" namespace Renderer { namespace Backend { namespace Dummy { CDevice::CDevice() { m_Name = "Dummy"; m_Version = "Unknown"; m_DriverInformation = "Unknown"; m_Extensions = {}; m_Backbuffer = CFramebuffer::Create(this); m_Capabilities.S3TC = true; m_Capabilities.ARBShaders = false; m_Capabilities.ARBShadersShadow = false; m_Capabilities.computeShaders = true; m_Capabilities.debugLabels = true; m_Capabilities.debugScopedLabels = true; m_Capabilities.multisampling = true; m_Capabilities.anisotropicFiltering = true; m_Capabilities.maxSampleCount = 4u; m_Capabilities.maxAnisotropy = 16.0f; m_Capabilities.maxTextureSize = 8192u; m_Capabilities.instancing = true; } CDevice::~CDevice() = default; void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings) { Script::SetProperty(rq, settings, "name", "dummy"); } std::unique_ptr CDevice::CreateCommandContext() { return CDeviceCommandContext::Create(this); } std::unique_ptr CDevice::CreateTexture( const char* UNUSED(name), const CTexture::Type type, const uint32_t usage, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& UNUSED(defaultSamplerDesc), const uint32_t MIPLevelCount, const uint32_t UNUSED(sampleCount)) { return CTexture::Create(this, type, usage, format, width, height, MIPLevelCount); } std::unique_ptr CDevice::CreateTexture2D( const char* name, const uint32_t usage, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { return CreateTexture(name, ITexture::Type::TEXTURE_2D, usage, format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount); } std::unique_ptr CDevice::CreateFramebuffer( const char*, ITexture*, ITexture*) { return CFramebuffer::Create(this); } std::unique_ptr CDevice::CreateFramebuffer( const char*, ITexture*, ITexture*, const CColor&) { return CFramebuffer::Create(this); } std::unique_ptr CDevice::CreateBuffer( const char*, const CBuffer::Type type, const uint32_t size, const bool dynamic) { return CBuffer::Create(this, type, size, dynamic); } std::unique_ptr CDevice::CreateShaderProgram( const CStr&, const CShaderDefines&) { return CShaderProgram::Create(this); } -void CDevice::AcquireNextBackbuffer() +bool CDevice::AcquireNextBackbuffer() { // We have nothing to acquire. + return true; } void CDevice::Present() { // We have nothing to present. } bool CDevice::IsTextureFormatSupported(const Format UNUSED(format)) const { return true; } bool CDevice::IsFramebufferFormatSupported(const Format UNUSED(format)) const { return true; } std::unique_ptr CreateDevice(SDL_Window* UNUSED(window)) { return std::make_unique(); } } // namespace Dummy } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/dummy/Device.h =================================================================== --- ps/trunk/source/renderer/backend/dummy/Device.h (revision 27183) +++ ps/trunk/source/renderer/backend/dummy/Device.h (revision 27184) @@ -1,107 +1,107 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_RENDERER_BACKEND_DUMMY_DEVICE #define INCLUDED_RENDERER_BACKEND_DUMMY_DEVICE #include "renderer/backend/dummy/DeviceForward.h" #include "renderer/backend/IDevice.h" class CShaderDefines; namespace Renderer { namespace Backend { namespace Dummy { class CDeviceCommandContext; class CDevice : public IDevice { public: CDevice(); ~CDevice() override; Backend GetBackend() const override { return Backend::DUMMY; } const std::string& GetName() const override { return m_Name; } const std::string& GetVersion() const override { return m_Version; } const std::string& GetDriverInformation() const override { return m_DriverInformation; } const std::vector& GetExtensions() const override { return m_Extensions; } void Report(const ScriptRequest& rq, JS::HandleValue settings) override; IFramebuffer* GetCurrentBackbuffer() override { return m_Backbuffer.get(); } std::unique_ptr CreateCommandContext() override; std::unique_ptr CreateTexture( const char* name, const ITexture::Type type, const uint32_t usage, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) override; std::unique_ptr CreateTexture2D( const char* name, const uint32_t usage, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount = 1, const uint32_t sampleCount = 1) override; std::unique_ptr CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment) override; std::unique_ptr CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment, const CColor& clearColor) override; std::unique_ptr CreateBuffer( const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) override; std::unique_ptr CreateShaderProgram( const CStr& name, const CShaderDefines& defines) override; - void AcquireNextBackbuffer() override; + bool AcquireNextBackbuffer() override; void Present() override; bool IsTextureFormatSupported(const Format format) const override; bool IsFramebufferFormatSupported(const Format format) const override; const Capabilities& GetCapabilities() const override { return m_Capabilities; } protected: std::string m_Name; std::string m_Version; std::string m_DriverInformation; std::vector m_Extensions; std::unique_ptr m_Backbuffer; Capabilities m_Capabilities{}; }; } // namespace Dummy } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_BACKEND_DUMMY_DEVICE Index: ps/trunk/source/renderer/backend/gl/Device.cpp =================================================================== --- ps/trunk/source/renderer/backend/gl/Device.cpp (revision 27183) +++ ps/trunk/source/renderer/backend/gl/Device.cpp (revision 27184) @@ -1,1015 +1,1016 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Device.h" #include "lib/external_libraries/libsdl.h" #include "lib/ogl.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Profile.h" #include "renderer/backend/gl/DeviceCommandContext.h" #include "renderer/backend/gl/Texture.h" #include "scriptinterface/JSON.h" #include "scriptinterface/Object.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptRequest.h" #if OS_WIN #include "lib/sysdep/os/win/wgfx.h" // We can't include wutil directly because GL headers conflict with Windows // until we use a proper GL loader. extern void* wutil_GetAppHDC(); #endif #include #include #include #if !CONFIG2_GLES && (defined(SDL_VIDEO_DRIVER_X11) || defined(SDL_VIDEO_DRIVER_WAYLAND)) #if defined(SDL_VIDEO_DRIVER_X11) #include #endif #if defined(SDL_VIDEO_DRIVER_WAYLAND) #include #endif #include #endif // !CONFIG2_GLES && (defined(SDL_VIDEO_DRIVER_X11) || defined(SDL_VIDEO_DRIVER_WAYLAND)) namespace Renderer { namespace Backend { namespace GL { namespace { std::string GetNameImpl() { // GL_VENDOR+GL_RENDERER are good enough here, so we don't use WMI to detect the cards. // On top of that WMI can cause crashes with Nvidia Optimus and some netbooks // see http://trac.wildfiregames.com/ticket/1952 // http://trac.wildfiregames.com/ticket/1575 char cardName[128]; const char* vendor = reinterpret_cast(glGetString(GL_VENDOR)); const char* renderer = reinterpret_cast(glGetString(GL_RENDERER)); // Happens if called before GL initialization. if (!vendor || !renderer) return {}; sprintf_s(cardName, std::size(cardName), "%s %s", vendor, renderer); // Remove crap from vendor names. (don't dare touch the model name - // it's too risky, there are too many different strings). #define SHORTEN(what, charsToKeep) \ if (!strncmp(cardName, what, std::size(what) - 1)) \ memmove(cardName + charsToKeep, cardName + std::size(what) - 1, (strlen(cardName) - (std::size(what) - 1) + 1) * sizeof(char)); SHORTEN("ATI Technologies Inc.", 3); SHORTEN("NVIDIA Corporation", 6); SHORTEN("S3 Graphics", 2); // returned by EnumDisplayDevices SHORTEN("S3 Graphics, Incorporated", 2); // returned by GL_VENDOR #undef SHORTEN return cardName; } std::string GetVersionImpl() { return reinterpret_cast(glGetString(GL_VERSION)); } std::string GetDriverInformationImpl() { const std::string version = GetVersionImpl(); std::string driverInfo; #if OS_WIN driverInfo = CStrW(wgfx_DriverInfo()).ToUTF8(); if (driverInfo.empty()) #endif { if (!version.empty()) { // Add "OpenGL" to differentiate this from the real driver version // (returned by platform-specific detect routines). driverInfo = std::string("OpenGL ") + version; } } if (driverInfo.empty()) return version; return version + " " + driverInfo; } std::vector GetExtensionsImpl() { std::vector extensions; const std::string exts = ogl_ExtensionString(); boost::split(extensions, exts, boost::algorithm::is_space(), boost::token_compress_on); std::sort(extensions.begin(), extensions.end()); return extensions; } void GLAD_API_PTR OnDebugMessage( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei UNUSED(length), const GLchar* message, const void* UNUSED(user_param)) { std::string debugSource = "unknown"; std::string debugType = "unknown"; std::string debugSeverity = "unknown"; switch (source) { case GL_DEBUG_SOURCE_API: debugSource = "the API"; break; case GL_DEBUG_SOURCE_WINDOW_SYSTEM: debugSource = "the window system"; break; case GL_DEBUG_SOURCE_SHADER_COMPILER: debugSource = "the shader compiler"; break; case GL_DEBUG_SOURCE_THIRD_PARTY: debugSource = "a third party"; break; case GL_DEBUG_SOURCE_APPLICATION: debugSource = "the application"; break; case GL_DEBUG_SOURCE_OTHER: debugSource = "somewhere"; break; } switch (type) { case GL_DEBUG_TYPE_ERROR: debugType = "error"; break; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: debugType = "deprecated behaviour"; break; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: debugType = "undefined behaviour"; break; case GL_DEBUG_TYPE_PORTABILITY: debugType = "portability"; break; case GL_DEBUG_TYPE_PERFORMANCE: debugType = "performance"; break; case GL_DEBUG_TYPE_OTHER: debugType = "other"; break; case GL_DEBUG_TYPE_MARKER: debugType = "marker"; break; case GL_DEBUG_TYPE_PUSH_GROUP: debugType = "push group"; break; case GL_DEBUG_TYPE_POP_GROUP: debugType = "pop group"; break; } switch (severity) { case GL_DEBUG_SEVERITY_HIGH: debugSeverity = "high"; break; case GL_DEBUG_SEVERITY_MEDIUM: debugSeverity = "medium"; break; case GL_DEBUG_SEVERITY_LOW: debugSeverity = "low"; break; case GL_DEBUG_SEVERITY_NOTIFICATION: debugSeverity = "notification"; break; } if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) { debug_printf( "OpenGL | %s: %s source: %s id %u: %s\n", debugSeverity.c_str(), debugType.c_str(), debugSource.c_str(), id, message); } else { LOGWARNING( "OpenGL | %s: %s source: %s id %u: %s\n", debugSeverity.c_str(), debugType.c_str(), debugSource.c_str(), id, message); } } } // anonymous namespace // static std::unique_ptr CDevice::Create(SDL_Window* window, const bool arb) { std::unique_ptr device(new CDevice()); if (window) { // According to https://wiki.libsdl.org/SDL_CreateWindow we don't need to // call SDL_GL_LoadLibrary if we have a window with SDL_WINDOW_OPENGL, // because it'll be called internally for the first created window. device->m_Window = window; device->m_Context = SDL_GL_CreateContext(device->m_Window); if (!device->m_Context) { LOGERROR("SDL_GL_CreateContext failed: '%s'", SDL_GetError()); return nullptr; } #if OS_WIN ogl_Init(SDL_GL_GetProcAddress, wutil_GetAppHDC()); #elif (defined(SDL_VIDEO_DRIVER_X11) || defined(SDL_VIDEO_DRIVER_WAYLAND)) && !CONFIG2_GLES SDL_SysWMinfo wminfo; // The info structure must be initialized with the SDL version. SDL_VERSION(&wminfo.version); if (!SDL_GetWindowWMInfo(window, &wminfo)) { LOGERROR("Failed to query SDL WM info: %s", SDL_GetError()); return nullptr; } switch (wminfo.subsystem) { #if defined(SDL_VIDEO_DRIVER_WAYLAND) case SDL_SYSWM_WAYLAND: // TODO: maybe we need to load X11 functions // dynamically as well. ogl_Init(SDL_GL_GetProcAddress, GetWaylandDisplay(device->m_Window), static_cast(wminfo.subsystem)); break; #endif #if defined(SDL_VIDEO_DRIVER_X11) case SDL_SYSWM_X11: ogl_Init(SDL_GL_GetProcAddress, GetX11Display(device->m_Window), static_cast(wminfo.subsystem)); break; #endif default: ogl_Init(SDL_GL_GetProcAddress, nullptr, static_cast(wminfo.subsystem)); break; } #else ogl_Init(SDL_GL_GetProcAddress); #endif } else { #if OS_WIN ogl_Init(SDL_GL_GetProcAddress, wutil_GetAppHDC()); #elif (defined(SDL_VIDEO_DRIVER_X11) || defined(SDL_VIDEO_DRIVER_WAYLAND)) && !CONFIG2_GLES bool initialized = false; // Currently we don't have access to the backend type without // the window. So we use hack to detect X11. #if defined(SDL_VIDEO_DRIVER_X11) Display* display = XOpenDisplay(NULL); if (display) { ogl_Init(SDL_GL_GetProcAddress, display, static_cast(SDL_SYSWM_X11)); initialized = true; } #endif #if defined(SDL_VIDEO_DRIVER_WAYLAND) if (!initialized) { // glad will find default EGLDisplay internally. ogl_Init(SDL_GL_GetProcAddress, nullptr, static_cast(SDL_SYSWM_WAYLAND)); initialized = true; } #endif if (!initialized) { LOGERROR("Can't initialize GL"); return nullptr; } #else ogl_Init(SDL_GL_GetProcAddress); #endif #if OS_WIN || defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES // Hack to stop things looking very ugly when scrolling in Atlas. ogl_SetVsyncEnabled(true); #endif } // If we don't have GL2.0 then we don't have GLSL in core. if (!arb && !ogl_HaveVersion(2, 0)) return nullptr; if ((ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", nullptr) // ARB && !ogl_HaveVersion(2, 0)) // GLSL || !ogl_HaveExtension("GL_ARB_vertex_buffer_object") // VBO || ogl_HaveExtensions(0, "GL_ARB_multitexture", "GL_EXT_draw_range_elements", nullptr) || (!ogl_HaveExtension("GL_EXT_framebuffer_object") && !ogl_HaveExtension("GL_ARB_framebuffer_object"))) { // It doesn't make sense to continue working here, because we're not // able to display anything. DEBUG_DISPLAY_FATAL_ERROR( L"Your graphics card doesn't appear to be fully compatible with OpenGL shaders." L" The game does not support pre-shader graphics cards." L" You are advised to try installing newer drivers and/or upgrade your graphics card." L" For more information, please see http://www.wildfiregames.com/forum/index.php?showtopic=16734" ); } device->m_ARB = arb; device->m_Name = GetNameImpl(); device->m_Version = GetVersionImpl(); device->m_DriverInformation = GetDriverInformationImpl(); device->m_Extensions = GetExtensionsImpl(); // Set packing parameters for uploading and downloading data. glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glEnable(GL_TEXTURE_2D); if (arb) { #if !CONFIG2_GLES glEnable(GL_VERTEX_PROGRAM_ARB); glEnable(GL_FRAGMENT_PROGRAM_ARB); #endif } device->m_Backbuffer = CFramebuffer::CreateBackbuffer(device.get()); Capabilities& capabilities = device->m_Capabilities; capabilities.ARBShaders = !ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", nullptr); if (capabilities.ARBShaders) capabilities.ARBShadersShadow = ogl_HaveExtension("GL_ARB_fragment_program_shadow"); capabilities.computeShaders = ogl_HaveVersion(4, 3) || ogl_HaveExtension("GL_ARB_compute_shader"); #if CONFIG2_GLES // Some GLES implementations have GL_EXT_texture_compression_dxt1 // but that only supports DXT1 so we can't use it. capabilities.S3TC = ogl_HaveExtensions(0, "GL_EXT_texture_compression_s3tc", nullptr) == 0; #else // Note: we don't bother checking for GL_S3_s3tc - it is incompatible // and irrelevant (was never widespread). capabilities.S3TC = ogl_HaveExtensions(0, "GL_ARB_texture_compression", "GL_EXT_texture_compression_s3tc", nullptr) == 0; #endif #if CONFIG2_GLES capabilities.multisampling = false; capabilities.maxSampleCount = 1; #else capabilities.multisampling = ogl_HaveVersion(3, 3) && ogl_HaveExtension("GL_ARB_multisample") && ogl_HaveExtension("GL_ARB_texture_multisample"); if (capabilities.multisampling) { // By default GL_MULTISAMPLE should be enabled, but enable it for buggy drivers. glEnable(GL_MULTISAMPLE); GLint maxSamples = 1; glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); capabilities.maxSampleCount = maxSamples; } #endif capabilities.anisotropicFiltering = ogl_HaveExtension("GL_EXT_texture_filter_anisotropic"); if (capabilities.anisotropicFiltering) { GLfloat maxAnisotropy = 1.0f; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy); capabilities.maxAnisotropy = maxAnisotropy; } GLint maxTextureSize = 1024; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); capabilities.maxTextureSize = maxTextureSize; #if CONFIG2_GLES const bool isDebugInCore = ogl_HaveVersion(3, 2); #else const bool isDebugInCore = ogl_HaveVersion(4, 3); #endif const bool hasDebug = isDebugInCore || ogl_HaveExtension("GL_KHR_debug"); if (hasDebug) { #ifdef NDEBUG bool enableDebugMessages = false; CFG_GET_VAL("renderer.backend.debugmessages", enableDebugMessages); capabilities.debugLabels = false; CFG_GET_VAL("renderer.backend.debuglabels", capabilities.debugLabels); capabilities.debugScopedLabels = false; CFG_GET_VAL("renderer.backend.debugscopedlabels", capabilities.debugScopedLabels); #else const bool enableDebugMessages = true; capabilities.debugLabels = true; capabilities.debugScopedLabels = true; #endif if (enableDebugMessages) { glEnable(GL_DEBUG_OUTPUT); #if !CONFIG2_GLES glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); #else #warning GLES without GL_DEBUG_OUTPUT_SYNCHRONOUS might call the callback from different threads which might be unsafe. #endif glDebugMessageCallback(OnDebugMessage, nullptr); // Filter out our own debug group messages const GLuint id = 0x0AD; glDebugMessageControl( GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PUSH_GROUP, GL_DONT_CARE, 1, &id, GL_FALSE); glDebugMessageControl( GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_POP_GROUP, GL_DONT_CARE, 1, &id, GL_FALSE); } } #if CONFIG2_GLES capabilities.instancing = false; #else capabilities.instancing = !device->m_ARB && (ogl_HaveVersion(3, 3) || (ogl_HaveExtension("GL_ARB_draw_instanced") && ogl_HaveExtension("GL_ARB_instanced_arrays"))); #endif return device; } CDevice::CDevice() = default; CDevice::~CDevice() { if (m_Context) SDL_GL_DeleteContext(m_Context); } void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings) { const char* errstr = "(error)"; Script::SetProperty(rq, settings, "name", m_ARB ? "glarb" : "gl"); #define INTEGER(id) do { \ GLint i = -1; \ glGetIntegerv(GL_##id, &i); \ if (ogl_SquelchError(GL_INVALID_ENUM)) \ Script::SetProperty(rq, settings, "GL_" #id, errstr); \ else \ Script::SetProperty(rq, settings, "GL_" #id, i); \ } while (false) #define INTEGER2(id) do { \ GLint i[2] = { -1, -1 }; \ glGetIntegerv(GL_##id, i); \ if (ogl_SquelchError(GL_INVALID_ENUM)) { \ Script::SetProperty(rq, settings, "GL_" #id "[0]", errstr); \ Script::SetProperty(rq, settings, "GL_" #id "[1]", errstr); \ } else { \ Script::SetProperty(rq, settings, "GL_" #id "[0]", i[0]); \ Script::SetProperty(rq, settings, "GL_" #id "[1]", i[1]); \ } \ } while (false) #define FLOAT(id) do { \ GLfloat f = std::numeric_limits::quiet_NaN(); \ glGetFloatv(GL_##id, &f); \ if (ogl_SquelchError(GL_INVALID_ENUM)) \ Script::SetProperty(rq, settings, "GL_" #id, errstr); \ else \ Script::SetProperty(rq, settings, "GL_" #id, f); \ } while (false) #define FLOAT2(id) do { \ GLfloat f[2] = { std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN() }; \ glGetFloatv(GL_##id, f); \ if (ogl_SquelchError(GL_INVALID_ENUM)) { \ Script::SetProperty(rq, settings, "GL_" #id "[0]", errstr); \ Script::SetProperty(rq, settings, "GL_" #id "[1]", errstr); \ } else { \ Script::SetProperty(rq, settings, "GL_" #id "[0]", f[0]); \ Script::SetProperty(rq, settings, "GL_" #id "[1]", f[1]); \ } \ } while (false) #define STRING(id) do { \ const char* c = (const char*)glGetString(GL_##id); \ if (!c) c = ""; \ if (ogl_SquelchError(GL_INVALID_ENUM)) c = errstr; \ Script::SetProperty(rq, settings, "GL_" #id, std::string(c)); \ } while (false) #define QUERY(target, pname) do { \ GLint i = -1; \ glGetQueryivARB(GL_##target, GL_##pname, &i); \ if (ogl_SquelchError(GL_INVALID_ENUM)) \ Script::SetProperty(rq, settings, "GL_" #target ".GL_" #pname, errstr); \ else \ Script::SetProperty(rq, settings, "GL_" #target ".GL_" #pname, i); \ } while (false) #define VERTEXPROGRAM(id) do { \ GLint i = -1; \ glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_##id, &i); \ if (ogl_SquelchError(GL_INVALID_ENUM)) \ Script::SetProperty(rq, settings, "GL_VERTEX_PROGRAM_ARB.GL_" #id, errstr); \ else \ Script::SetProperty(rq, settings, "GL_VERTEX_PROGRAM_ARB.GL_" #id, i); \ } while (false) #define FRAGMENTPROGRAM(id) do { \ GLint i = -1; \ glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_##id, &i); \ if (ogl_SquelchError(GL_INVALID_ENUM)) \ Script::SetProperty(rq, settings, "GL_FRAGMENT_PROGRAM_ARB.GL_" #id, errstr); \ else \ Script::SetProperty(rq, settings, "GL_FRAGMENT_PROGRAM_ARB.GL_" #id, i); \ } while (false) #define BOOL(id) INTEGER(id) ogl_WarnIfError(); // Core OpenGL 1.3: // (We don't bother checking extension strings for anything older than 1.3; // it'll just produce harmless warnings) STRING(VERSION); STRING(VENDOR); STRING(RENDERER); STRING(EXTENSIONS); #if !CONFIG2_GLES INTEGER(MAX_CLIP_PLANES); #endif INTEGER(SUBPIXEL_BITS); #if !CONFIG2_GLES INTEGER(MAX_3D_TEXTURE_SIZE); #endif INTEGER(MAX_TEXTURE_SIZE); INTEGER(MAX_CUBE_MAP_TEXTURE_SIZE); INTEGER2(MAX_VIEWPORT_DIMS); #if !CONFIG2_GLES BOOL(RGBA_MODE); BOOL(INDEX_MODE); BOOL(DOUBLEBUFFER); BOOL(STEREO); #endif FLOAT2(ALIASED_POINT_SIZE_RANGE); FLOAT2(ALIASED_LINE_WIDTH_RANGE); #if !CONFIG2_GLES INTEGER(MAX_ELEMENTS_INDICES); INTEGER(MAX_ELEMENTS_VERTICES); INTEGER(MAX_TEXTURE_UNITS); #endif INTEGER(SAMPLE_BUFFERS); INTEGER(SAMPLES); // TODO: compressed texture formats INTEGER(RED_BITS); INTEGER(GREEN_BITS); INTEGER(BLUE_BITS); INTEGER(ALPHA_BITS); #if !CONFIG2_GLES INTEGER(INDEX_BITS); #endif INTEGER(DEPTH_BITS); INTEGER(STENCIL_BITS); #if !CONFIG2_GLES // Core OpenGL 2.0 (treated as extensions): if (ogl_HaveExtension("GL_EXT_texture_lod_bias")) { FLOAT(MAX_TEXTURE_LOD_BIAS_EXT); } if (ogl_HaveExtension("GL_ARB_occlusion_query")) { QUERY(SAMPLES_PASSED, QUERY_COUNTER_BITS); } if (ogl_HaveExtension("GL_ARB_shading_language_100")) { STRING(SHADING_LANGUAGE_VERSION_ARB); } if (ogl_HaveExtension("GL_ARB_vertex_shader")) { INTEGER(MAX_VERTEX_ATTRIBS_ARB); INTEGER(MAX_VERTEX_UNIFORM_COMPONENTS_ARB); INTEGER(MAX_VARYING_FLOATS_ARB); INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB); INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB); } if (ogl_HaveExtension("GL_ARB_fragment_shader")) { INTEGER(MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB); } if (ogl_HaveExtension("GL_ARB_vertex_shader") || ogl_HaveExtension("GL_ARB_fragment_shader") || ogl_HaveExtension("GL_ARB_vertex_program") || ogl_HaveExtension("GL_ARB_fragment_program")) { INTEGER(MAX_TEXTURE_IMAGE_UNITS_ARB); INTEGER(MAX_TEXTURE_COORDS_ARB); } if (ogl_HaveExtension("GL_ARB_draw_buffers")) { INTEGER(MAX_DRAW_BUFFERS_ARB); } // Core OpenGL 3.0: if (ogl_HaveExtension("GL_EXT_gpu_shader4")) { INTEGER(MIN_PROGRAM_TEXEL_OFFSET_EXT); // no _EXT version of these in glext.h INTEGER(MAX_PROGRAM_TEXEL_OFFSET_EXT); } if (ogl_HaveExtension("GL_EXT_framebuffer_object")) { INTEGER(MAX_COLOR_ATTACHMENTS_EXT); INTEGER(MAX_RENDERBUFFER_SIZE_EXT); } if (ogl_HaveExtension("GL_EXT_framebuffer_multisample")) { INTEGER(MAX_SAMPLES_EXT); } if (ogl_HaveExtension("GL_EXT_texture_array")) { INTEGER(MAX_ARRAY_TEXTURE_LAYERS_EXT); } if (ogl_HaveExtension("GL_EXT_transform_feedback")) { INTEGER(MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_EXT); INTEGER(MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_EXT); INTEGER(MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_EXT); } // Other interesting extensions: if (ogl_HaveExtension("GL_EXT_timer_query") || ogl_HaveExtension("GL_ARB_timer_query")) { QUERY(TIME_ELAPSED, QUERY_COUNTER_BITS); } if (ogl_HaveExtension("GL_ARB_timer_query")) { QUERY(TIMESTAMP, QUERY_COUNTER_BITS); } if (ogl_HaveExtension("GL_EXT_texture_filter_anisotropic")) { FLOAT(MAX_TEXTURE_MAX_ANISOTROPY_EXT); } if (ogl_HaveExtension("GL_ARB_texture_rectangle")) { INTEGER(MAX_RECTANGLE_TEXTURE_SIZE_ARB); } if (m_ARB) { if (ogl_HaveExtension("GL_ARB_vertex_program") || ogl_HaveExtension("GL_ARB_fragment_program")) { INTEGER(MAX_PROGRAM_MATRICES_ARB); INTEGER(MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB); } if (ogl_HaveExtension("GL_ARB_vertex_program")) { VERTEXPROGRAM(MAX_PROGRAM_ENV_PARAMETERS_ARB); VERTEXPROGRAM(MAX_PROGRAM_LOCAL_PARAMETERS_ARB); VERTEXPROGRAM(MAX_PROGRAM_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_TEMPORARIES_ARB); VERTEXPROGRAM(MAX_PROGRAM_PARAMETERS_ARB); VERTEXPROGRAM(MAX_PROGRAM_ATTRIBS_ARB); VERTEXPROGRAM(MAX_PROGRAM_ADDRESS_REGISTERS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEMPORARIES_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_PARAMETERS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ATTRIBS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB); if (ogl_HaveExtension("GL_ARB_fragment_program")) { // The spec seems to say these should be supported, but // Mesa complains about them so let's not bother /* VERTEXPROGRAM(MAX_PROGRAM_ALU_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_TEX_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_TEX_INDIRECTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB); */ } } if (ogl_HaveExtension("GL_ARB_fragment_program")) { FRAGMENTPROGRAM(MAX_PROGRAM_ENV_PARAMETERS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_LOCAL_PARAMETERS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_ALU_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_TEX_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_TEX_INDIRECTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_TEMPORARIES_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_PARAMETERS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_ATTRIBS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEMPORARIES_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_PARAMETERS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ATTRIBS_ARB); if (ogl_HaveExtension("GL_ARB_vertex_program")) { // The spec seems to say these should be supported, but // Intel drivers on Windows complain about them so let's not bother /* FRAGMENTPROGRAM(MAX_PROGRAM_ADDRESS_REGISTERS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB); */ } } } if (ogl_HaveExtension("GL_ARB_geometry_shader4")) { INTEGER(MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_ARB); INTEGER(MAX_GEOMETRY_OUTPUT_VERTICES_ARB); INTEGER(MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB); INTEGER(MAX_GEOMETRY_UNIFORM_COMPONENTS_ARB); INTEGER(MAX_GEOMETRY_VARYING_COMPONENTS_ARB); INTEGER(MAX_VERTEX_VARYING_COMPONENTS_ARB); } #else // CONFIG2_GLES // Core OpenGL ES 2.0: STRING(SHADING_LANGUAGE_VERSION); INTEGER(MAX_VERTEX_ATTRIBS); INTEGER(MAX_VERTEX_UNIFORM_VECTORS); INTEGER(MAX_VARYING_VECTORS); INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS); INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS); INTEGER(MAX_FRAGMENT_UNIFORM_VECTORS); INTEGER(MAX_TEXTURE_IMAGE_UNITS); INTEGER(MAX_RENDERBUFFER_SIZE); #endif // CONFIG2_GLES // TODO: Support OpenGL platforms which don't use GLX as well. #if defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES #define GLXQCR_INTEGER(id) do { \ unsigned int i = UINT_MAX; \ if (glXQueryCurrentRendererIntegerMESA(id, &i)) \ Script::SetProperty(rq, settings, #id, i); \ } while (false) #define GLXQCR_INTEGER2(id) do { \ unsigned int i[2] = { UINT_MAX, UINT_MAX }; \ if (glXQueryCurrentRendererIntegerMESA(id, i)) { \ Script::SetProperty(rq, settings, #id "[0]", i[0]); \ Script::SetProperty(rq, settings, #id "[1]", i[1]); \ } \ } while (false) #define GLXQCR_INTEGER3(id) do { \ unsigned int i[3] = { UINT_MAX, UINT_MAX, UINT_MAX }; \ if (glXQueryCurrentRendererIntegerMESA(id, i)) { \ Script::SetProperty(rq, settings, #id "[0]", i[0]); \ Script::SetProperty(rq, settings, #id "[1]", i[1]); \ Script::SetProperty(rq, settings, #id "[2]", i[2]); \ } \ } while (false) #define GLXQCR_STRING(id) do { \ const char* str = glXQueryCurrentRendererStringMESA(id); \ if (str) \ Script::SetProperty(rq, settings, #id ".string", str); \ } while (false) SDL_SysWMinfo wminfo; SDL_VERSION(&wminfo.version); const int ret = SDL_GetWindowWMInfo(m_Window, &wminfo); if (ret && wminfo.subsystem == SDL_SYSWM_X11) { Display* dpy = wminfo.info.x11.display; int scrnum = DefaultScreen(dpy); const char* glxexts = glXQueryExtensionsString(dpy, scrnum); Script::SetProperty(rq, settings, "glx_extensions", glxexts); if (strstr(glxexts, "GLX_MESA_query_renderer") && glXQueryCurrentRendererIntegerMESA && glXQueryCurrentRendererStringMESA) { GLXQCR_INTEGER(GLX_RENDERER_VENDOR_ID_MESA); GLXQCR_INTEGER(GLX_RENDERER_DEVICE_ID_MESA); GLXQCR_INTEGER3(GLX_RENDERER_VERSION_MESA); GLXQCR_INTEGER(GLX_RENDERER_ACCELERATED_MESA); GLXQCR_INTEGER(GLX_RENDERER_VIDEO_MEMORY_MESA); GLXQCR_INTEGER(GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA); GLXQCR_INTEGER(GLX_RENDERER_PREFERRED_PROFILE_MESA); GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA); GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA); GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA); GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA); GLXQCR_STRING(GLX_RENDERER_VENDOR_ID_MESA); GLXQCR_STRING(GLX_RENDERER_DEVICE_ID_MESA); } } #endif // SDL_VIDEO_DRIVER_X11 } std::unique_ptr CDevice::CreateCommandContext() { std::unique_ptr commandContet = CDeviceCommandContext::Create(this); m_ActiveCommandContext = commandContet.get(); return commandContet; } std::unique_ptr CDevice::CreateTexture( const char* name, const ITexture::Type type, const uint32_t usage, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { return CTexture::Create(this, name, type, usage, format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount); } std::unique_ptr CDevice::CreateTexture2D( const char* name, const uint32_t usage, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { return CreateTexture(name, CTexture::Type::TEXTURE_2D, usage, format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount); } std::unique_ptr CDevice::CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment) { return CreateFramebuffer(name, colorAttachment, depthStencilAttachment, CColor(0.0f, 0.0f, 0.0f, 0.0f)); } std::unique_ptr CDevice::CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment, const CColor& clearColor) { return CFramebuffer::Create( this, name, colorAttachment->As(), depthStencilAttachment->As(), clearColor); } std::unique_ptr CDevice::CreateBuffer( const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) { return CBuffer::Create(this, name, type, size, dynamic); } std::unique_ptr CDevice::CreateShaderProgram( const CStr& name, const CShaderDefines& defines) { return CShaderProgram::Create(this, name, defines); } -void CDevice::AcquireNextBackbuffer() +bool CDevice::AcquireNextBackbuffer() { ENSURE(!m_BackbufferAcquired); m_BackbufferAcquired = true; + return true; } void CDevice::Present() { ENSURE(m_BackbufferAcquired); m_BackbufferAcquired = false; if (m_Window) { PROFILE3("swap buffers"); SDL_GL_SwapWindow(m_Window); ogl_WarnIfError(); } bool checkGLErrorAfterSwap = false; CFG_GET_VAL("gl.checkerrorafterswap", checkGLErrorAfterSwap); #if defined(NDEBUG) if (!checkGLErrorAfterSwap) return; #endif PROFILE3("error check"); // We have to check GL errors after SwapBuffer to avoid possible // synchronizations during rendering. if (GLenum err = glGetError()) ONCE(LOGERROR("GL error %s (0x%04x) occurred", ogl_GetErrorName(err), err)); } bool CDevice::IsTextureFormatSupported(const Format format) const { bool supported = false; switch (format) { case Format::UNDEFINED: break; case Format::R8G8B8_UNORM: FALLTHROUGH; case Format::R8G8B8A8_UNORM: FALLTHROUGH; case Format::A8_UNORM: FALLTHROUGH; case Format::L8_UNORM: supported = true; break; case Format::R32_SFLOAT: FALLTHROUGH; case Format::R32G32_SFLOAT: FALLTHROUGH; case Format::R32G32B32_SFLOAT: FALLTHROUGH; case Format::R32G32B32A32_SFLOAT: break; case Format::D16: FALLTHROUGH; case Format::D24: FALLTHROUGH; case Format::D32: supported = true; break; case Format::D24_S8: #if !CONFIG2_GLES supported = true; #endif break; case Format::BC1_RGB_UNORM: FALLTHROUGH; case Format::BC1_RGBA_UNORM: FALLTHROUGH; case Format::BC2_UNORM: FALLTHROUGH; case Format::BC3_UNORM: supported = m_Capabilities.S3TC; break; default: break; } return supported; } bool CDevice::IsFramebufferFormatSupported(const Format format) const { bool supported = false; switch (format) { case Format::UNDEFINED: break; #if !CONFIG2_GLES case Format::R8_UNORM: supported = ogl_HaveVersion(3, 0); break; #endif case Format::R8G8B8A8_UNORM: supported = true; break; default: break; } return supported; } std::unique_ptr CreateDevice(SDL_Window* window, const bool arb) { return GL::CDevice::Create(window, arb); } } // namespace GL } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/gl/Device.h =================================================================== --- ps/trunk/source/renderer/backend/gl/Device.h (revision 27183) +++ ps/trunk/source/renderer/backend/gl/Device.h (revision 27184) @@ -1,136 +1,136 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_RENDERER_BACKEND_GL_DEVICE #define INCLUDED_RENDERER_BACKEND_GL_DEVICE #include "renderer/backend/Format.h" #include "renderer/backend/gl/Buffer.h" #include "renderer/backend/gl/DeviceForward.h" #include "renderer/backend/gl/Framebuffer.h" #include "renderer/backend/gl/ShaderProgram.h" #include "renderer/backend/gl/Texture.h" #include "renderer/backend/IDevice.h" #include "scriptinterface/ScriptForward.h" #include #include #include typedef struct SDL_Window SDL_Window; typedef void* SDL_GLContext; namespace Renderer { namespace Backend { namespace GL { class CDeviceCommandContext; class CDevice final : public IDevice { public: ~CDevice() override; /** * Creates the GL device and the GL context for the window if it presents. */ static std::unique_ptr Create(SDL_Window* window, const bool arb); Backend GetBackend() const override { return m_ARB ? Backend::GL_ARB : Backend::GL; } const std::string& GetName() const override { return m_Name; } const std::string& GetVersion() const override { return m_Version; } const std::string& GetDriverInformation() const override { return m_DriverInformation; } const std::vector& GetExtensions() const override { return m_Extensions; } void Report(const ScriptRequest& rq, JS::HandleValue settings) override; IFramebuffer* GetCurrentBackbuffer() override { return m_Backbuffer.get(); } std::unique_ptr CreateCommandContext() override; CDeviceCommandContext* GetActiveCommandContext() { return m_ActiveCommandContext; } std::unique_ptr CreateTexture( const char* name, const ITexture::Type type, const uint32_t usage, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) override; std::unique_ptr CreateTexture2D( const char* name, const uint32_t usage, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount = 1, const uint32_t sampleCount = 1) override; std::unique_ptr CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment) override; std::unique_ptr CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment, const CColor& clearColor) override; std::unique_ptr CreateBuffer( const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) override; std::unique_ptr CreateShaderProgram( const CStr& name, const CShaderDefines& defines) override; - void AcquireNextBackbuffer() override; + bool AcquireNextBackbuffer() override; void Present() override; bool IsTextureFormatSupported(const Format format) const override; bool IsFramebufferFormatSupported(const Format format) const override; const Capabilities& GetCapabilities() const override { return m_Capabilities; } private: CDevice(); SDL_Window* m_Window = nullptr; SDL_GLContext m_Context = nullptr; bool m_ARB = false; std::string m_Name; std::string m_Version; std::string m_DriverInformation; std::vector m_Extensions; // GL can have the only one command context at once. // TODO: remove as soon as we have no GL code outside backend, currently // it's used only as a helper for transition. CDeviceCommandContext* m_ActiveCommandContext = nullptr; std::unique_ptr m_Backbuffer; bool m_BackbufferAcquired = false; Capabilities m_Capabilities{}; }; } // namespace GL } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_BACKEND_GL_DEVICE Index: ps/trunk/source/renderer/backend/vulkan/Device.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Device.cpp (revision 27183) +++ ps/trunk/source/renderer/backend/vulkan/Device.cpp (revision 27184) @@ -1,182 +1,183 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Device.h" #include "lib/external_libraries/libsdl.h" #include "scriptinterface/JSON.h" #include "scriptinterface/Object.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptRequest.h" #if SDL_VERSION_ATLEAST(2, 0, 8) #include #endif namespace Renderer { namespace Backend { namespace Vulkan { // static std::unique_ptr CDevice::Create(SDL_Window* UNUSED(window)) { std::unique_ptr device(new CDevice()); return device; } CDevice::CDevice() = default; CDevice::~CDevice() = default; void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings) { Script::SetProperty(rq, settings, "name", "vulkan"); std::string vulkanSupport = "unsupported"; // According to http://wiki.libsdl.org/SDL_Vulkan_LoadLibrary the following // functionality is supported since SDL 2.0.8. #if SDL_VERSION_ATLEAST(2, 0, 8) if (!SDL_Vulkan_LoadLibrary(nullptr)) { void* vkGetInstanceProcAddr = SDL_Vulkan_GetVkGetInstanceProcAddr(); if (vkGetInstanceProcAddr) vulkanSupport = "supported"; else vulkanSupport = "noprocaddr"; SDL_Vulkan_UnloadLibrary(); } else { vulkanSupport = "cantload"; } #endif Script::SetProperty(rq, settings, "status", vulkanSupport); } IFramebuffer* CDevice::GetCurrentBackbuffer() { return nullptr; } std::unique_ptr CDevice::CreateCommandContext() { return nullptr; } std::unique_ptr CDevice::CreateTexture( const char* name, const ITexture::Type type, const uint32_t usage, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { UNUSED2(name); UNUSED2(type); UNUSED2(usage); UNUSED2(format); UNUSED2(width); UNUSED2(height); UNUSED2(defaultSamplerDesc); UNUSED2(MIPLevelCount); UNUSED2(sampleCount); return nullptr; } std::unique_ptr CDevice::CreateTexture2D( const char* name, const uint32_t usage, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { return CreateTexture( name, ITexture::Type::TEXTURE_2D, usage, format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount); } std::unique_ptr CDevice::CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment) { UNUSED2(name); UNUSED2(colorAttachment); UNUSED2(depthStencilAttachment); return nullptr; } std::unique_ptr CDevice::CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment, const CColor& clearColor) { UNUSED2(name); UNUSED2(colorAttachment); UNUSED2(depthStencilAttachment); UNUSED2(clearColor); return nullptr; } std::unique_ptr CDevice::CreateBuffer( const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) { UNUSED2(name); UNUSED2(type); UNUSED2(size); UNUSED2(dynamic); return nullptr; } std::unique_ptr CDevice::CreateShaderProgram( const CStr& name, const CShaderDefines& defines) { UNUSED2(name); UNUSED2(defines); return nullptr; } -void CDevice::AcquireNextBackbuffer() +bool CDevice::AcquireNextBackbuffer() { + return false; } void CDevice::Present() { } bool CDevice::IsTextureFormatSupported(const Format format) const { UNUSED2(format); return false; } bool CDevice::IsFramebufferFormatSupported(const Format format) const { UNUSED2(format); return false; } std::unique_ptr CreateDevice(SDL_Window* window) { return Vulkan::CDevice::Create(window); } } // namespace Vulkan } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/Device.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Device.h (revision 27183) +++ ps/trunk/source/renderer/backend/vulkan/Device.h (revision 27184) @@ -1,111 +1,111 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_RENDERER_BACKEND_VULKAN_DEVICE #define INCLUDED_RENDERER_BACKEND_VULKAN_DEVICE #include "renderer/backend/IDevice.h" #include "renderer/backend/vulkan/DeviceForward.h" #include "scriptinterface/ScriptForward.h" #include typedef struct SDL_Window SDL_Window; namespace Renderer { namespace Backend { namespace Vulkan { class CDevice : public IDevice { public: /** * Creates the Vulkan device. */ static std::unique_ptr Create(SDL_Window* window); ~CDevice() override; Backend GetBackend() const override { return Backend::VULKAN; } const std::string& GetName() const override { return m_Name; } const std::string& GetVersion() const override { return m_Version; } const std::string& GetDriverInformation() const override { return m_DriverInformation; } const std::vector& GetExtensions() const override { return m_Extensions; } void Report(const ScriptRequest& rq, JS::HandleValue settings) override; IFramebuffer* GetCurrentBackbuffer() override; std::unique_ptr CreateCommandContext() override; std::unique_ptr CreateTexture( const char* name, const ITexture::Type type, const uint32_t usage, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) override; std::unique_ptr CreateTexture2D( const char* name, const uint32_t usage, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount = 1, const uint32_t sampleCount = 1) override; std::unique_ptr CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment) override; std::unique_ptr CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment, const CColor& clearColor) override; std::unique_ptr CreateBuffer( const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) override; std::unique_ptr CreateShaderProgram( const CStr& name, const CShaderDefines& defines) override; - void AcquireNextBackbuffer() override; + bool AcquireNextBackbuffer() override; void Present() override; bool IsTextureFormatSupported(const Format format) const override; bool IsFramebufferFormatSupported(const Format format) const override; const Capabilities& GetCapabilities() const override { return m_Capabilities; } private: CDevice(); std::string m_Name; std::string m_Version; std::string m_DriverInformation; std::vector m_Extensions; Capabilities m_Capabilities{}; }; } // namespace Vulkan } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_BACKEND_VULKAN_DEVICE Index: ps/trunk/source/tools/atlas/GameInterface/View.cpp =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/View.cpp (revision 27183) +++ ps/trunk/source/tools/atlas/GameInterface/View.cpp (revision 27184) @@ -1,463 +1,464 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "View.h" #include "ActorViewer.h" #include "GameLoop.h" #include "Messages.h" #include "SimState.h" #include "graphics/Canvas2D.h" #include "graphics/CinemaManager.h" #include "graphics/GameView.h" #include "graphics/ParticleManager.h" #include "graphics/UnitManager.h" #include "lib/timer.h" #include "lib/utf8.h" #include "maths/MathUtil.h" #include "ps/Game.h" #include "ps/GameSetup/GameSetup.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "renderer/backend/IDevice.h" #include "renderer/DebugRenderer.h" #include "renderer/Renderer.h" #include "renderer/SceneRenderer.h" #include "simulation2/components/ICmpObstructionManager.h" #include "simulation2/components/ICmpParticleManager.h" #include "simulation2/components/ICmpPathfinder.h" #include "simulation2/Simulation2.h" #include "soundmanager/ISoundManager.h" extern void (*Atlas_GLSwapBuffers)(void* context); extern int g_xres, g_yres; ////////////////////////////////////////////////////////////////////////// void AtlasView::SetParam(const std::wstring& UNUSED(name), bool UNUSED(value)) { } void AtlasView::SetParam(const std::wstring& UNUSED(name), const AtlasMessage::Color& UNUSED(value)) { } void AtlasView::SetParam(const std::wstring& UNUSED(name), const std::wstring& UNUSED(value)) { } void AtlasView::SetParam(const std::wstring& UNUSED(name), int UNUSED(value)) { } ////////////////////////////////////////////////////////////////////////// AtlasViewActor::AtlasViewActor() : m_SpeedMultiplier(1.f), m_ActorViewer(new ActorViewer()) { } AtlasViewActor::~AtlasViewActor() { delete m_ActorViewer; } void AtlasViewActor::Update(float realFrameLength) { m_ActorViewer->Update(realFrameLength * m_SpeedMultiplier, realFrameLength); } void AtlasViewActor::Render() { SViewPort vp = { 0, 0, g_xres, g_yres }; CCamera& camera = GetCamera(); camera.SetViewPort(vp); camera.SetPerspectiveProjection(2.f, 512.f, DEGTORAD(20.f)); camera.UpdateFrustum(); m_ActorViewer->Render(); Atlas_GLSwapBuffers((void*)g_AtlasGameLoop->glCanvas); } CCamera& AtlasViewActor::GetCamera() { return m_Camera; } CSimulation2* AtlasViewActor::GetSimulation2() { return m_ActorViewer->GetSimulation2(); } entity_id_t AtlasViewActor::GetEntityId(AtlasMessage::ObjectID UNUSED(obj)) { return m_ActorViewer->GetEntity(); } bool AtlasViewActor::WantsHighFramerate() { if (m_SpeedMultiplier != 0.f) return true; return false; } void AtlasViewActor::SetEnabled(bool enabled) { m_ActorViewer->SetEnabled(enabled); } void AtlasViewActor::SetSpeedMultiplier(float speedMultiplier) { m_SpeedMultiplier = speedMultiplier; } ActorViewer& AtlasViewActor::GetActorViewer() { return *m_ActorViewer; } void AtlasViewActor::SetParam(const std::wstring& name, bool value) { if (name == L"wireframe") g_Renderer.GetSceneRenderer().SetModelRenderMode(value ? WIREFRAME : SOLID); else if (name == L"walk") m_ActorViewer->SetWalkEnabled(value); else if (name == L"ground") m_ActorViewer->SetGroundEnabled(value); // TODO: this causes corruption of WaterManager's global state // which should be asociated with terrain or simulation instead // see http://trac.wildfiregames.com/ticket/2692 //else if (name == L"water") //m_ActorViewer->SetWaterEnabled(value); else if (name == L"shadows") m_ActorViewer->ToggleShadows(); else if (name == L"stats") m_ActorViewer->SetStatsEnabled(value); else if (name == L"bounding_box") m_ActorViewer->SetBoundingBoxesEnabled(value); else if (name == L"axes_marker") m_ActorViewer->SetAxesMarkerEnabled(value); } void AtlasViewActor::SetParam(const std::wstring& name, int value) { if (name == L"prop_points") m_ActorViewer->SetPropPointsMode(value); } void AtlasViewActor::SetParam(const std::wstring& UNUSED(name), const AtlasMessage::Color& UNUSED(value)) { } ////////////////////////////////////////////////////////////////////////// AtlasViewGame::AtlasViewGame() : m_SpeedMultiplier(0.f), m_IsTesting(false), m_DrawMoveTool(false) { ENSURE(g_Game); } AtlasViewGame::~AtlasViewGame() { for (const std::pair& p : m_SavedStates) delete p.second; } CSimulation2* AtlasViewGame::GetSimulation2() { return g_Game->GetSimulation2(); } void AtlasViewGame::Update(float realFrameLength) { const float actualFrameLength = realFrameLength * m_SpeedMultiplier; // Clean up any entities destroyed during UI message processing g_Game->GetSimulation2()->FlushDestroyedEntities(); if (m_SpeedMultiplier == 0.f) { // Update unit interpolation g_Game->Interpolate(0.0, realFrameLength); } else { // Update the whole world // (Tell the game update not to interpolate graphics - we'll do that // ourselves) g_Game->Update(actualFrameLength, false); // Interpolate the graphics - we only want to do this once per visual frame, // not in every call to g_Game->Update g_Game->Interpolate(actualFrameLength, realFrameLength); } // Run sound idle tasks every frame. if (g_SoundManager) g_SoundManager->IdleTask(); // Cinematic motion should be independent of simulation update, so we can // preview the cinematics by themselves g_Game->GetView()->GetCinema()->Update(realFrameLength); } void AtlasViewGame::Render() { - g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer(); + if (!g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer()) + return; SViewPort vp = { 0, 0, g_xres, g_yres }; CCamera& camera = GetCamera(); camera.SetViewPort(vp); camera.SetProjectionFromCamera(*g_Game->GetView()->GetCamera()); camera.UpdateFrustum(); g_Renderer.RenderFrame(false); Atlas_GLSwapBuffers((void*)g_AtlasGameLoop->glCanvas); // In case of atlas the device's present will do only internal stuff // without calling a real backbuffer swap. g_VideoMode.GetBackendDevice()->Present(); } void AtlasViewGame::DrawCinemaPathTool() { if (!m_DrawMoveTool) return; const CVector3D focus = m_MoveTool; const CVector3D camera = GetCamera().GetOrientation().GetTranslation(); const float scale = (focus - camera).Length(); const float axisLength = scale / 10.0f; const float lineWidth = scale / 1e3f; g_Renderer.GetDebugRenderer().DrawLine( focus, focus + CVector3D(axisLength, 0, 0), CColor(1.0f, 0.0f, 0.0f, 1.0f), lineWidth, false); g_Renderer.GetDebugRenderer().DrawLine( focus, focus + CVector3D(0, axisLength, 0), CColor(0.0f, 1.0f, 0.0f, 1.0f), lineWidth, false); g_Renderer.GetDebugRenderer().DrawLine( focus, focus + CVector3D(0, 0, axisLength), CColor(0.0f, 0.0f, 1.0f, 1.0f), lineWidth, false); } void AtlasViewGame::DrawOverlays(CCanvas2D& canvas) { if (m_Bandbox.left >= m_Bandbox.right || m_Bandbox.top >= m_Bandbox.bottom) return; const std::vector outerPoints = { m_Bandbox.TopLeft() + CVector2D(-1.0f, -1.0f), m_Bandbox.TopRight() + CVector2D(1.0f, -1.0f), m_Bandbox.BottomRight() + CVector2D(1.0f, 1.0f), m_Bandbox.BottomLeft() + CVector2D(-1.0f, 1.0f), m_Bandbox.TopLeft() + CVector2D(-1.0f, -1.0f) }; canvas.DrawLine(outerPoints, 1.5f, CColor(0.0f, 0.0f, 0.0f, 1.0f)); const std::vector innerPoints = { m_Bandbox.TopLeft(), m_Bandbox.TopRight(), m_Bandbox.BottomRight(), m_Bandbox.BottomLeft(), m_Bandbox.TopLeft() }; canvas.DrawLine(innerPoints, 1.5f, CColor(1.0f, 1.0f, 1.0f, 1.0f)); } void AtlasViewGame::SetParam(const std::wstring& name, bool value) { if (name == L"priorities") g_Renderer.GetSceneRenderer().SetDisplayTerrainPriorities(value); else if (name == L"movetool") m_DrawMoveTool = value; } void AtlasViewGame::SetParam(const std::wstring& name, float value) { if (name == L"movetool_x") m_MoveTool.X = value; else if (name == L"movetool_y") m_MoveTool.Y = value; else if (name == L"movetool_z") m_MoveTool.Z = value; } void AtlasViewGame::SetParam(const std::wstring& name, const std::wstring& value) { if (name == L"passability") { m_DisplayPassability = CStrW(value).ToUTF8(); CmpPtr cmpPathfinder(*GetSimulation2(), SYSTEM_ENTITY); if (cmpPathfinder) { if (!value.empty()) cmpPathfinder->SetAtlasOverlay(true, cmpPathfinder->GetPassabilityClass(m_DisplayPassability)); else cmpPathfinder->SetAtlasOverlay(false); } } } CCamera& AtlasViewGame::GetCamera() { return *g_Game->GetView()->GetCamera(); } bool AtlasViewGame::WantsHighFramerate() { if (g_Game->GetView()->GetCinema()->IsPlaying()) return true; if (m_SpeedMultiplier != 0.f) return true; return false; } void AtlasViewGame::SetSpeedMultiplier(float speed) { m_SpeedMultiplier = speed; } void AtlasViewGame::SetTesting(bool testing) { m_IsTesting = testing; // If we're testing, particles should freeze on pause (like in-game), otherwise they keep going CmpPtr cmpParticleManager(*GetSimulation2(), SYSTEM_ENTITY); if (cmpParticleManager) cmpParticleManager->SetUseSimTime(m_IsTesting); } void AtlasViewGame::SaveState(const std::wstring& label) { delete m_SavedStates[label]; // in case it already exists m_SavedStates[label] = SimState::Freeze(); } void AtlasViewGame::RestoreState(const std::wstring& label) { SimState* simState = m_SavedStates[label]; if (! simState) return; simState->Thaw(); } std::wstring AtlasViewGame::DumpState(bool binary) { std::stringstream stream; if (binary) { if (! g_Game->GetSimulation2()->SerializeState(stream)) return L"(internal error)"; // We can't return raw binary data, because we want to handle it with wxJS which // doesn't like \0 bytes in strings, so return it as hex static const char digits[] = "0123456789abcdef"; std::string str = stream.str(); std::wstring ret; ret.reserve(str.length()*3); for (size_t i = 0; i < str.length(); ++i) { ret += digits[(unsigned char)str[i] >> 4]; ret += digits[(unsigned char)str[i] & 0x0f]; ret += ' '; } return ret; } else { if (! g_Game->GetSimulation2()->DumpDebugState(stream)) return L"(internal error)"; return wstring_from_utf8(stream.str()); } } void AtlasViewGame::SetBandbox(bool visible, float x0, float y0, float x1, float y1) { if (visible) { // Make sure corners are arranged in correct order if (x0 > x1) std::swap(x0, x1); if (y0 > y1) std::swap(y0, y1); m_Bandbox = CRect(x0, y0, x1, y1); } else { m_Bandbox = CRect{}; } } ////////////////////////////////////////////////////////////////////////// AtlasViewNone* view_None = NULL; AtlasViewGame* view_Game = NULL; AtlasViewActor* view_Actor = NULL; AtlasView::~AtlasView() { } AtlasView* AtlasView::GetView(int /*eRenderView*/ view) { switch (view) { case AtlasMessage::eRenderView::NONE: return AtlasView::GetView_None(); case AtlasMessage::eRenderView::GAME: return AtlasView::GetView_Game(); case AtlasMessage::eRenderView::ACTOR: return AtlasView::GetView_Actor(); default: debug_warn(L"Invalid view type"); return AtlasView::GetView_None(); } } AtlasView* AtlasView::GetView_None() { if (! view_None) view_None = new AtlasViewNone(); return view_None; } AtlasViewGame* AtlasView::GetView_Game() { if (! view_Game) view_Game = new AtlasViewGame(); return view_Game; } AtlasViewActor* AtlasView::GetView_Actor() { if (! view_Actor) view_Actor = new AtlasViewActor(); return view_Actor; } void AtlasView::DestroyViews() { delete view_None; view_None = NULL; delete view_Game; view_Game = NULL; delete view_Actor; view_Actor = NULL; }