Index: ps/trunk/source/renderer/backend/IShaderProgram.h =================================================================== --- ps/trunk/source/renderer/backend/IShaderProgram.h (revision 26900) +++ ps/trunk/source/renderer/backend/IShaderProgram.h (revision 26901) @@ -1,61 +1,67 @@ /* 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_ISHADERPROGRAM #define INCLUDED_RENDERER_BACKEND_ISHADERPROGRAM #include "lib/file/vfs/vfs_path.h" #include "ps/CStrIntern.h" #include "renderer/backend/IDeviceObject.h" namespace Renderer { namespace Backend { enum class VertexAttributeStream : uint32_t { POSITION, NORMAL, COLOR, UV0, UV1, UV2, UV3, UV4, UV5, UV6, UV7, }; +enum class VertexAttributeRate : uint32_t +{ + PER_VERTEX, + PER_INSTANCE +}; + /** * IShaderProgram is a container for multiple shaders of different types. */ class IShaderProgram : public IDeviceObject { public: virtual int32_t GetBindingSlot(const CStrIntern name) const = 0; virtual std::vector GetFileDependencies() const = 0; }; } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_BACKEND_ISHADERPROGRAM Index: ps/trunk/source/renderer/backend/gl/ShaderProgram.h =================================================================== --- ps/trunk/source/renderer/backend/gl/ShaderProgram.h (revision 26900) +++ ps/trunk/source/renderer/backend/gl/ShaderProgram.h (revision 26901) @@ -1,146 +1,146 @@ /* 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_SHADERPROGRAM #define INCLUDED_RENDERER_BACKEND_GL_SHADERPROGRAM #include "lib/ogl.h" #include "lib/file/vfs/vfs_path.h" #include "ps/CStrForward.h" #include "renderer/backend/Format.h" #include "renderer/backend/gl/Texture.h" #include "renderer/backend/IShaderProgram.h" #include #include struct CColor; class CMatrix3D; class CVector3D; class CShaderDefines; class CStrIntern; namespace Renderer { namespace Backend { namespace GL { class CDevice; /** * A compiled vertex+fragment shader program. * The implementation may use GL_ARB_{vertex,fragment}_program (ARB assembly syntax) * or GL_ARB_{vertex,fragment}_shader (GLSL), or may use hard-coded fixed-function * multitexturing setup code; the difference is hidden from the caller. * * Texture/uniform IDs are typically strings, corresponding to the names defined in * the shader .xml file. Alternatively (and more efficiently, if used very frequently), * call GetBindingSlot and pass its return value as the ID. * Setting uniforms that the shader .xml doesn't support is harmless. * * For a high-level overview of shaders and materials, see * http://trac.wildfiregames.com/wiki/MaterialSystem */ class CShaderProgram : public IShaderProgram { NONCOPYABLE(CShaderProgram); public: typedef CStrIntern attrib_id_t; static std::unique_ptr Create( CDevice* device, const CStr& name, const CShaderDefines& baseDefines); ~CShaderProgram() override; /** * Binds the shader into the GL context. Call this before calling Uniform() * or trying to render with it. */ virtual void Bind(CShaderProgram* previousShaderProgram) = 0; /** * Unbinds the shader from the GL context. Call this after rendering with it. */ virtual void Unbind() = 0; struct TextureUnit { GLenum type; GLenum target; GLint unit; }; virtual TextureUnit GetTextureUnit(const int32_t bindingSlot) = 0; virtual void SetUniform( const int32_t bindingSlot, const float value) = 0; virtual void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY) = 0; virtual void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ) = 0; virtual void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ, const float valueW) = 0; virtual void SetUniform( const int32_t bindingSlot, PS::span values) = 0; // Vertex attribute pointers (equivalent to glVertexPointer etc). virtual void VertexAttribPointer( const VertexAttributeStream stream, const Format format, - const uint32_t offset, const uint32_t stride, const void* data); + const uint32_t offset, const uint32_t stride, + const VertexAttributeRate rate, const void* data); bool IsStreamActive(const VertexAttributeStream stream) const; /** * Checks that all the required vertex attributes have been set. * Call this before calling Draw/DrawIndexed etc to avoid potential crashes. */ void AssertPointersBound(); protected: CShaderProgram(int streamflags); void VertexPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer); void NormalPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer); void ColorPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer); void TexCoordPointer(GLenum texture, const Renderer::Backend::Format format, GLsizei stride, const void* pointer); - void VertexAttribPointer(attrib_id_t id, const Renderer::Backend::Format format, GLboolean normalized, GLsizei stride, const void* pointer); int m_StreamFlags; // Non-GLSL client state handling: void BindClientStates(); void UnbindClientStates(); int m_ValidStreams; // which streams have been specified via VertexPointer etc since the last Bind }; } // namespace GL } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_BACKEND_GL_SHADERPROGRAM Index: ps/trunk/binaries/data/mods/public/shaders/glsl/minimap.vs =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/glsl/minimap.vs (revision 26900) +++ ps/trunk/binaries/data/mods/public/shaders/glsl/minimap.vs (revision 26901) @@ -1,49 +1,60 @@ #version 110 uniform mat4 transform; uniform mat4 textureTransform; #if MINIMAP_MASK uniform mat4 maskTextureTransform; varying vec2 v_maskUV; #endif #if MINIMAP_BASE || MINIMAP_LOS || MINIMAP_MASK attribute vec3 a_vertex; attribute vec2 a_uv0; #endif #if MINIMAP_BASE || MINIMAP_LOS varying vec2 v_tex; #endif #if MINIMAP_POINT attribute vec2 a_vertex; attribute vec3 a_color; varying vec3 color; #endif #if MINIMAP_LINE attribute vec2 a_vertex; #endif +#if MINIMAP_POINT && USE_GPU_INSTANCING +attribute vec2 a_uv1; +attribute vec4 a_uv2; + +uniform float width; +#endif + void main() { - #if MINIMAP_BASE || MINIMAP_LOS - gl_Position = transform * vec4(a_vertex, 1.0); - v_tex = (textureTransform * vec4(a_uv0, 0.0, 1.0)).xy; - #endif - - #if MINIMAP_MASK - v_maskUV = (maskTextureTransform * vec4(a_uv0, 0.0, 1.0)).xy; - #endif - - #if MINIMAP_POINT - gl_Position = transform * vec4(a_vertex, 0.0, 1.0); - color = a_color; - #endif - - #if MINIMAP_LINE - gl_Position = transform * vec4(a_vertex, 0.0, 1.0); - #endif +#if MINIMAP_BASE || MINIMAP_LOS + gl_Position = transform * vec4(a_vertex, 1.0); + v_tex = (textureTransform * vec4(a_uv0, 0.0, 1.0)).xy; +#endif + +#if MINIMAP_MASK + v_maskUV = (maskTextureTransform * vec4(a_uv0, 0.0, 1.0)).xy; +#endif + +#if MINIMAP_POINT +#if USE_GPU_INSTANCING + gl_Position = transform * vec4(a_vertex * width + a_uv1, 0.0, 1.0); +#else + gl_Position = transform * vec4(a_vertex, 0.0, 1.0); +#endif + color = a_color; +#endif // MINIMAP_POINT + +#if MINIMAP_LINE + gl_Position = transform * vec4(a_vertex, 0.0, 1.0); +#endif } Index: ps/trunk/binaries/data/mods/public/shaders/glsl/minimap.xml =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/glsl/minimap.xml (revision 26900) +++ ps/trunk/binaries/data/mods/public/shaders/glsl/minimap.xml (revision 26901) @@ -1,12 +1,13 @@ + Index: ps/trunk/source/graphics/Canvas2D.cpp =================================================================== --- ps/trunk/source/graphics/Canvas2D.cpp (revision 26900) +++ ps/trunk/source/graphics/Canvas2D.cpp (revision 26901) @@ -1,377 +1,383 @@ /* 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 "Canvas2D.h" #include "graphics/Color.h" #include "graphics/ShaderManager.h" #include "graphics/TextRenderer.h" #include "graphics/TextureManager.h" #include "gui/GUIMatrix.h" #include "maths/Rect.h" #include "maths/Vector2D.h" #include "ps/CStrInternStatic.h" #include "renderer/Renderer.h" #include namespace { // Array of 2D elements unrolled into 1D array. using PlaneArray2D = std::array; struct SBindingSlots { int32_t transform; int32_t colorAdd; int32_t colorMul; int32_t grayscaleFactor; int32_t tex; }; inline void DrawTextureImpl( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CTexturePtr& texture, const PlaneArray2D& vertices, PlaneArray2D uvs, const CColor& multiply, const CColor& add, const float grayscaleFactor, const SBindingSlots& bindingSlots) { texture->UploadBackendTextureIfNeeded(deviceCommandContext); deviceCommandContext->SetTexture( bindingSlots.tex, texture->GetBackendTexture()); for (size_t idx = 0; idx < uvs.size(); idx += 2) { if (texture->GetWidth() > 0.0f) uvs[idx + 0] /= texture->GetWidth(); if (texture->GetHeight() > 0.0f) uvs[idx + 1] /= texture->GetHeight(); } deviceCommandContext->SetUniform(bindingSlots.colorAdd, add.AsFloatArray()); deviceCommandContext->SetUniform(bindingSlots.colorMul, multiply.AsFloatArray()); deviceCommandContext->SetUniform(bindingSlots.grayscaleFactor, grayscaleFactor); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 0); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 1); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1); - deviceCommandContext->SetVertexBufferData(0, vertices.data()); - deviceCommandContext->SetVertexBufferData(1, uvs.data()); + deviceCommandContext->SetVertexBufferData( + 0, vertices.data(), vertices.size() * sizeof(vertices[0])); + deviceCommandContext->SetVertexBufferData( + 1, uvs.data(), uvs.size() * sizeof(uvs[0])); deviceCommandContext->Draw(0, vertices.size() / 2); } } // anonymous namespace class CCanvas2D::Impl { public: Impl(Renderer::Backend::IDeviceCommandContext* deviceCommandContext) : DeviceCommandContext(deviceCommandContext) { } void BindTechIfNeeded() { if (Tech) return; CShaderDefines defines; Tech = g_Renderer.GetShaderManager().LoadEffect(str_canvas2d, defines); // The canvas technique must be loaded because we can't render UI without it. ENSURE(Tech); DeviceCommandContext->SetGraphicsPipelineState( Tech->GetGraphicsPipelineStateDesc()); DeviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = Tech->GetShader(); BindingSlots.transform = shader->GetBindingSlot(str_transform); BindingSlots.colorAdd = shader->GetBindingSlot(str_colorAdd); BindingSlots.colorMul = shader->GetBindingSlot(str_colorMul); BindingSlots.grayscaleFactor = shader->GetBindingSlot(str_grayscaleFactor); BindingSlots.tex = shader->GetBindingSlot(str_tex); const CMatrix3D transform = GetDefaultGuiMatrix(); DeviceCommandContext->SetUniform( BindingSlots.transform, transform.AsFloatArray()); } void UnbindTech() { if (!Tech) return; DeviceCommandContext->EndPass(); Tech.reset(); } Renderer::Backend::IDeviceCommandContext* DeviceCommandContext = nullptr; CShaderTechniquePtr Tech; // We assume that the shader can't be destroyed while it's bound. So these // bindings remain valid while the shader is alive. SBindingSlots BindingSlots; }; CCanvas2D::CCanvas2D( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) : m(std::make_unique(deviceCommandContext)) { } CCanvas2D::~CCanvas2D() { Flush(); } void CCanvas2D::DrawLine(const std::vector& points, const float width, const CColor& color) { if (points.empty()) return; // We could reuse the terrain line building, but it uses 3D space instead of // 2D. So it can be less optimal for a canvas. // Adding a single pixel line with alpha gradient to reduce the aliasing // effect. const float halfWidth = width * 0.5f + 1.0f; struct PointIndex { size_t index; float length; CVector2D normal; }; // Normal for the last index is undefined. std::vector pointsIndices; pointsIndices.reserve(points.size()); pointsIndices.emplace_back(PointIndex{0, 0.0f, CVector2D()}); for (size_t index = 0; index < points.size();) { size_t nextIndex = index + 1; CVector2D direction; float length = 0.0f; while (nextIndex < points.size()) { direction = points[nextIndex] - points[pointsIndices.back().index]; length = direction.Length(); if (length >= halfWidth * 2.0f) { direction /= length; break; } ++nextIndex; } if (nextIndex == points.size()) break; pointsIndices.back().length = length; pointsIndices.back().normal = CVector2D(-direction.Y, direction.X); pointsIndices.emplace_back(PointIndex{nextIndex, 0.0f, CVector2D()}); index = nextIndex; } if (pointsIndices.size() <= 1) return; std::vector> vertices; std::vector> uvs; std::vector indices; const size_t reserveSize = 2 * pointsIndices.size() - 1; vertices.reserve(reserveSize); uvs.reserve(reserveSize); indices.reserve(reserveSize * 12); auto addVertices = [&vertices, &uvs, &indices, &halfWidth](const CVector2D& p1, const CVector2D& p2) { if (!vertices.empty()) { const u16 lastVertexIndex = static_cast(vertices.size() * 3 - 1); ENSURE(lastVertexIndex >= 2); // First vertical half of the segment. indices.emplace_back(lastVertexIndex - 2); indices.emplace_back(lastVertexIndex - 1); indices.emplace_back(lastVertexIndex + 2); indices.emplace_back(lastVertexIndex - 2); indices.emplace_back(lastVertexIndex + 2); indices.emplace_back(lastVertexIndex + 1); // Second vertical half of the segment. indices.emplace_back(lastVertexIndex - 1); indices.emplace_back(lastVertexIndex); indices.emplace_back(lastVertexIndex + 3); indices.emplace_back(lastVertexIndex - 1); indices.emplace_back(lastVertexIndex + 3); indices.emplace_back(lastVertexIndex + 2); } vertices.emplace_back(std::array{p1, (p1 + p2) / 2.0f, p2}); uvs.emplace_back(std::array{ CVector2D(0.0f, 0.0f), CVector2D(std::max(1.0f, halfWidth - 1.0f), 0.0f), CVector2D(0.0f, 0.0f)}); }; addVertices( points[pointsIndices.front().index] - pointsIndices.front().normal * halfWidth, points[pointsIndices.front().index] + pointsIndices.front().normal * halfWidth); // For each pair of adjacent segments we need to add smooth transition. for (size_t index = 0; index + 2 < pointsIndices.size(); ++index) { const PointIndex& pointIndex = pointsIndices[index]; const PointIndex& nextPointIndex = pointsIndices[index + 1]; // Angle between adjacent segments. const float cosAlpha = pointIndex.normal.Dot(nextPointIndex.normal); constexpr float EPS = 1e-3f; // Use a simple segment if adjacent segments are almost codirectional. if (cosAlpha > 1.0f - EPS) { addVertices( points[pointIndex.index] - pointIndex.normal * halfWidth, points[pointIndex.index] + pointIndex.normal * halfWidth); } else { addVertices( points[nextPointIndex.index] - pointIndex.normal * halfWidth, points[nextPointIndex.index] + pointIndex.normal * halfWidth); // Average normal between adjacent segments. We might want to rotate it but // for now we assume that it's enough for current line widths. const CVector2D normal = cosAlpha < -1.0f + EPS ? CVector2D(pointIndex.normal.Y, -pointIndex.normal.X) : ((pointIndex.normal + nextPointIndex.normal) / 2.0f).Normalized(); addVertices( points[nextPointIndex.index] - normal * halfWidth, points[nextPointIndex.index] + normal * halfWidth); addVertices( points[nextPointIndex.index] - nextPointIndex.normal * halfWidth, points[nextPointIndex.index] + nextPointIndex.normal * halfWidth); } // We use 16-bit indices, it means that we can't use more than 64K vertices. const size_t requiredFreeSpace = 3 * 4; if (vertices.size() * 3 + requiredFreeSpace >= 65536) break; } addVertices( points[pointsIndices.back().index] - pointsIndices[pointsIndices.size() - 2].normal * halfWidth, points[pointsIndices.back().index] + pointsIndices[pointsIndices.size() - 2].normal * halfWidth); m->BindTechIfNeeded(); m->DeviceCommandContext->SetTexture( m->BindingSlots.tex, g_Renderer.GetTextureManager().GetAlphaGradientTexture()->GetBackendTexture()); const CColor colorAdd(0.0f, 0.0f, 0.0f, 0.0f); m->DeviceCommandContext->SetUniform( m->BindingSlots.colorAdd, colorAdd.AsFloatArray()); m->DeviceCommandContext->SetUniform( m->BindingSlots.colorMul, color.AsFloatArray()); m->DeviceCommandContext->SetUniform( m->BindingSlots.grayscaleFactor, 0.0f); m->DeviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 0); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); m->DeviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 1); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1); - m->DeviceCommandContext->SetVertexBufferData(0, vertices.data()); - m->DeviceCommandContext->SetVertexBufferData(1, uvs.data()); + m->DeviceCommandContext->SetVertexBufferData(0, vertices.data(), vertices.size() * sizeof(vertices[0])); + m->DeviceCommandContext->SetVertexBufferData(1, uvs.data(), uvs.size() * sizeof(uvs[0])); - m->DeviceCommandContext->SetIndexBufferData(indices.data()); + m->DeviceCommandContext->SetIndexBufferData(indices.data(), indices.size() * sizeof(indices[0])); m->DeviceCommandContext->DrawIndexed(0, indices.size(), 0); } void CCanvas2D::DrawRect(const CRect& rect, const CColor& color) { const PlaneArray2D uvs { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; const PlaneArray2D vertices = { rect.left, rect.bottom, rect.right, rect.bottom, rect.right, rect.top, rect.left, rect.bottom, rect.right, rect.top, rect.left, rect.top }; m->BindTechIfNeeded(); DrawTextureImpl( m->DeviceCommandContext, g_Renderer.GetTextureManager().GetTransparentTexture(), vertices, uvs, CColor(0.0f, 0.0f, 0.0f, 0.0f), color, 0.0f, m->BindingSlots); } void CCanvas2D::DrawTexture(CTexturePtr texture, const CRect& destination) { DrawTexture(texture, destination, CRect(0, 0, texture->GetWidth(), texture->GetHeight()), CColor(1.0f, 1.0f, 1.0f, 1.0f), CColor(0.0f, 0.0f, 0.0f, 0.0f), 0.0f); } void CCanvas2D::DrawTexture( CTexturePtr texture, const CRect& destination, const CRect& source, const CColor& multiply, const CColor& add, const float grayscaleFactor) { const PlaneArray2D uvs = { source.left, source.bottom, source.right, source.bottom, source.right, source.top, source.left, source.bottom, source.right, source.top, source.left, source.top }; const PlaneArray2D vertices = { destination.left, destination.bottom, destination.right, destination.bottom, destination.right, destination.top, destination.left, destination.bottom, destination.right, destination.top, destination.left, destination.top }; m->BindTechIfNeeded(); DrawTextureImpl( m->DeviceCommandContext, texture, vertices, uvs, multiply, add, grayscaleFactor, m->BindingSlots); } void CCanvas2D::DrawText(CTextRenderer& textRenderer) { m->BindTechIfNeeded(); m->DeviceCommandContext->SetUniform( m->BindingSlots.grayscaleFactor, 0.0f); textRenderer.Render(m->DeviceCommandContext, m->Tech->GetShader(), GetDefaultGuiMatrix()); } void CCanvas2D::Flush() { m->UnbindTech(); } Index: ps/trunk/source/graphics/LOSTexture.cpp =================================================================== --- ps/trunk/source/graphics/LOSTexture.cpp (revision 26900) +++ ps/trunk/source/graphics/LOSTexture.cpp (revision 26901) @@ -1,440 +1,444 @@ /* 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 "LOSTexture.h" #include "graphics/ShaderManager.h" #include "lib/bits.h" #include "lib/config2.h" #include "ps/CLogger.h" #include "ps/CStrInternStatic.h" #include "ps/Game.h" #include "ps/Profile.h" #include "renderer/backend/IDevice.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/TimeManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/helpers/Los.h" /* The LOS bitmap is computed with one value per LOS vertex, based on CCmpRangeManager's visibility information. The bitmap is then blurred using an NxN filter (in particular a 7-tap Binomial filter as an efficient integral approximation of a Gaussian). To implement the blur efficiently without using extra memory for a second copy of the bitmap, we generate the bitmap with (N-1)/2 pixels of padding on each side, then the blur shifts the image back into the corner. The blurred bitmap is then uploaded into a GL texture for use by the renderer. */ // Blur with a NxN filter, where N = g_BlurSize must be an odd number. // Keep it in relation to the number of impassable tiles in MAP_EDGE_TILES. static const size_t g_BlurSize = 7; // Alignment (in bytes) of the pixel data passed into texture uploading. // This must be a multiple of GL_UNPACK_ALIGNMENT, which ought to be 1 (since // that's what we set it to) but in some weird cases appears to have a different // value. (See Trac #2594). Multiples of 4 are possibly good for performance anyway. static const size_t g_SubTextureAlignment = 4; CLOSTexture::CLOSTexture(CSimulation2& simulation) : m_Simulation(simulation) { if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS()) CreateShader(); } CLOSTexture::~CLOSTexture() { m_SmoothFramebuffers[0].reset(); m_SmoothFramebuffers[1].reset(); if (m_Texture) DeleteTexture(); } // Create the LOS texture engine. Should be ran only once. bool CLOSTexture::CreateShader() { m_SmoothTech = g_Renderer.GetShaderManager().LoadEffect(str_los_interp); Renderer::Backend::IShaderProgram* shader = m_SmoothTech->GetShader(); m_ShaderInitialized = m_SmoothTech && shader; if (!m_ShaderInitialized) { LOGERROR("Failed to load SmoothLOS shader, disabling."); g_RenderingOptions.SetSmoothLOS(false); return false; } return true; } void CLOSTexture::DeleteTexture() { m_Texture.reset(); m_SmoothTextures[0].reset(); m_SmoothTextures[1].reset(); } void CLOSTexture::MakeDirty() { m_Dirty = true; } Renderer::Backend::ITexture* CLOSTexture::GetTextureSmooth() { if (CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS()) return GetTexture(); else return m_SmoothTextures[m_WhichTexture].get(); } void CLOSTexture::InterpolateLOS(Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { const bool skipSmoothLOS = CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS(); if (!skipSmoothLOS && !m_ShaderInitialized) { if (!CreateShader()) return; // RecomputeTexture will not cause the ConstructTexture to run. // Force the textures to be created. DeleteTexture(); ConstructTexture(deviceCommandContext); m_Dirty = true; } if (m_Dirty) { RecomputeTexture(deviceCommandContext); m_Dirty = false; } if (skipSmoothLOS) return; GPU_SCOPED_LABEL(deviceCommandContext, "Render LOS texture"); deviceCommandContext->SetFramebuffer(m_SmoothFramebuffers[m_WhichTexture].get()); deviceCommandContext->SetGraphicsPipelineState( m_SmoothTech->GetGraphicsPipelineStateDesc()); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = m_SmoothTech->GetShader(); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_losTex1), m_Texture.get()); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_losTex2), m_SmoothTextures[m_WhichTexture].get()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_delta), static_cast(g_Renderer.GetTimeManager().GetFrameDelta() * 4.0f)); const SViewPort oldVp = g_Renderer.GetViewport(); const SViewPort vp = { 0, 0, static_cast(m_Texture->GetWidth()), static_cast(m_Texture->GetHeight()) }; g_Renderer.SetViewport(vp); float quadVerts[] = { 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f }; float quadTex[] = { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 0); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 1); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1); - deviceCommandContext->SetVertexBufferData(0, quadVerts); - deviceCommandContext->SetVertexBufferData(1, quadTex); + deviceCommandContext->SetVertexBufferData( + 0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0])); + deviceCommandContext->SetVertexBufferData( + 1, quadTex, std::size(quadTex) * sizeof(quadTex[0])); deviceCommandContext->Draw(0, 6); g_Renderer.SetViewport(oldVp); deviceCommandContext->EndPass(); deviceCommandContext->SetFramebuffer( deviceCommandContext->GetDevice()->GetCurrentBackbuffer()); m_WhichTexture = 1u - m_WhichTexture; } Renderer::Backend::ITexture* CLOSTexture::GetTexture() { ENSURE(!m_Dirty); return m_Texture.get(); } const CMatrix3D& CLOSTexture::GetTextureMatrix() { ENSURE(!m_Dirty); return m_TextureMatrix; } const CMatrix3D& CLOSTexture::GetMinimapTextureMatrix() { ENSURE(!m_Dirty); return m_MinimapTextureMatrix; } void CLOSTexture::ConstructTexture(Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); if (!cmpRangeManager) return; m_MapSize = cmpRangeManager->GetVerticesPerSide(); const size_t textureSize = round_up_to_pow2(round_up((size_t)m_MapSize + g_BlurSize - 1, g_SubTextureAlignment)); Renderer::Backend::IDevice* backendDevice = deviceCommandContext->GetDevice(); const Renderer::Backend::Sampler::Desc defaultSamplerDesc = Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE); m_Texture = backendDevice->CreateTexture2D("LOSTexture", Renderer::Backend::Format::A8_UNORM, textureSize, textureSize, defaultSamplerDesc); // Initialise texture with SoD color, for the areas we don't // overwrite with uploading later. std::unique_ptr texData = std::make_unique(textureSize * textureSize); memset(texData.get(), 0x00, textureSize * textureSize); if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS()) { m_SmoothTextures[0] = backendDevice->CreateTexture2D("LOSSmoothTexture0", Renderer::Backend::Format::A8_UNORM, textureSize, textureSize, defaultSamplerDesc); m_SmoothTextures[1] = backendDevice->CreateTexture2D("LOSSmoothTexture1", Renderer::Backend::Format::A8_UNORM, textureSize, textureSize, defaultSamplerDesc); m_SmoothFramebuffers[0] = backendDevice->CreateFramebuffer("LOSSmoothFramebuffer0", m_SmoothTextures[0].get(), nullptr); m_SmoothFramebuffers[1] = backendDevice->CreateFramebuffer("LOSSmoothFramebuffer1", m_SmoothTextures[1].get(), nullptr); if (!m_SmoothFramebuffers[0] || !m_SmoothFramebuffers[1]) { LOGERROR("Failed to create LOS framebuffers"); g_RenderingOptions.SetSmoothLOS(false); } deviceCommandContext->UploadTexture( m_SmoothTextures[0].get(), Renderer::Backend::Format::A8_UNORM, texData.get(), textureSize * textureSize); deviceCommandContext->UploadTexture( m_SmoothTextures[1].get(), Renderer::Backend::Format::A8_UNORM, texData.get(), textureSize * textureSize); } deviceCommandContext->UploadTexture( m_Texture.get(), Renderer::Backend::Format::A8_UNORM, texData.get(), textureSize * textureSize); texData.reset(); { // Texture matrix: We want to map // world pos (0, y, 0) (i.e. first vertex) // onto texcoord (0.5/texsize, 0.5/texsize) (i.e. middle of first texel); // world pos ((mapsize-1)*cellsize, y, (mapsize-1)*cellsize) (i.e. last vertex) // onto texcoord ((mapsize-0.5) / texsize, (mapsize-0.5) / texsize) (i.e. middle of last texel) float s = (m_MapSize-1) / static_cast(textureSize * (m_MapSize-1) * LOS_TILE_SIZE); float t = 0.5f / textureSize; m_TextureMatrix.SetZero(); m_TextureMatrix._11 = s; m_TextureMatrix._23 = s; m_TextureMatrix._14 = t; m_TextureMatrix._24 = t; m_TextureMatrix._44 = 1; } { // Minimap matrix: We want to map UV (0,0)-(1,1) onto (0,0)-(mapsize/texsize, mapsize/texsize) float s = m_MapSize / (float)textureSize; m_MinimapTextureMatrix.SetZero(); m_MinimapTextureMatrix._11 = s; m_MinimapTextureMatrix._22 = s; m_MinimapTextureMatrix._44 = 1; } } void CLOSTexture::RecomputeTexture(Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { // If the map was resized, delete and regenerate the texture if (m_Texture) { CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); if (!cmpRangeManager || m_MapSize != cmpRangeManager->GetVerticesPerSide()) DeleteTexture(); } bool recreated = false; if (!m_Texture) { ConstructTexture(deviceCommandContext); recreated = true; } PROFILE("recompute LOS texture"); size_t pitch; const size_t dataSize = GetBitmapSize(m_MapSize, m_MapSize, &pitch); ENSURE(pitch * m_MapSize <= dataSize); std::unique_ptr losData = std::make_unique(dataSize); CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); if (!cmpRangeManager) return; CLosQuerier los(cmpRangeManager->GetLosQuerier(g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer())); GenerateBitmap(los, &losData[0], m_MapSize, m_MapSize, pitch); if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS() && recreated) { deviceCommandContext->UploadTextureRegion( m_SmoothTextures[0].get(), Renderer::Backend::Format::A8_UNORM, losData.get(), pitch * m_MapSize, 0, 0, pitch, m_MapSize); deviceCommandContext->UploadTextureRegion( m_SmoothTextures[1].get(), Renderer::Backend::Format::A8_UNORM, losData.get(), pitch * m_MapSize, 0, 0, pitch, m_MapSize); } deviceCommandContext->UploadTextureRegion( m_Texture.get(), Renderer::Backend::Format::A8_UNORM, losData.get(), pitch * m_MapSize, 0, 0, pitch, m_MapSize); } size_t CLOSTexture::GetBitmapSize(size_t w, size_t h, size_t* pitch) { *pitch = round_up(w + g_BlurSize - 1, g_SubTextureAlignment); return *pitch * (h + g_BlurSize - 1); } void CLOSTexture::GenerateBitmap(const CLosQuerier& los, u8* losData, size_t w, size_t h, size_t pitch) { u8 *dataPtr = losData; // Initialise the top padding for (size_t j = 0; j < g_BlurSize/2; ++j) for (size_t i = 0; i < pitch; ++i) *dataPtr++ = 0; for (size_t j = 0; j < h; ++j) { // Initialise the left padding for (size_t i = 0; i < g_BlurSize/2; ++i) *dataPtr++ = 0; // Fill in the visibility data for (size_t i = 0; i < w; ++i) { if (los.IsVisible_UncheckedRange(i, j)) *dataPtr++ = 255; else if (los.IsExplored_UncheckedRange(i, j)) *dataPtr++ = 127; else *dataPtr++ = 0; } // Initialise the right padding for (size_t i = 0; i < pitch - w - g_BlurSize/2; ++i) *dataPtr++ = 0; } // Initialise the bottom padding for (size_t j = 0; j < g_BlurSize/2; ++j) for (size_t i = 0; i < pitch; ++i) *dataPtr++ = 0; // Horizontal blur: for (size_t j = g_BlurSize/2; j < h + g_BlurSize/2; ++j) { for (size_t i = 0; i < w; ++i) { u8* d = &losData[i+j*pitch]; *d = ( 1*d[0] + 6*d[1] + 15*d[2] + 20*d[3] + 15*d[4] + 6*d[5] + 1*d[6] ) / 64; } } // Vertical blur: for (size_t j = 0; j < h; ++j) { for (size_t i = 0; i < w; ++i) { u8* d = &losData[i+j*pitch]; *d = ( 1*d[0*pitch] + 6*d[1*pitch] + 15*d[2*pitch] + 20*d[3*pitch] + 15*d[4*pitch] + 6*d[5*pitch] + 1*d[6*pitch] ) / 64; } } } Index: ps/trunk/source/graphics/MiniMapTexture.cpp =================================================================== --- ps/trunk/source/graphics/MiniMapTexture.cpp (revision 26900) +++ ps/trunk/source/graphics/MiniMapTexture.cpp (revision 26901) @@ -1,626 +1,725 @@ /* 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 "MiniMapTexture.h" #include "graphics/GameView.h" #include "graphics/LOSTexture.h" #include "graphics/MiniPatch.h" #include "graphics/ShaderManager.h" #include "graphics/ShaderProgramPtr.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TerritoryTexture.h" #include "graphics/TextureManager.h" #include "lib/bits.h" #include "lib/timer.h" #include "maths/Vector2D.h" #include "ps/ConfigDB.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "ps/Game.h" +#include "ps/VideoMode.h" #include "ps/World.h" #include "ps/XML/Xeromyces.h" #include "renderer/backend/IDevice.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/SceneRenderer.h" #include "renderer/WaterManager.h" #include "scriptinterface/Object.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpMinimap.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/system/ParamNode.h" +#include + namespace { // Set max drawn entities to 64K / 4 for now, which is more than enough. // 4 is the number of vertices per entity. // TODO: we should be cleverer about drawing them to reduce clutter, // f.e. use instancing. const size_t MAX_ENTITIES_DRAWN = 65536 / 4; const size_t MAX_ICON_COUNT = 128; const size_t FINAL_TEXTURE_SIZE = 512; unsigned int ScaleColor(unsigned int color, float x) { unsigned int r = unsigned(float(color & 0xff) * x); unsigned int g = unsigned(float((color >> 8) & 0xff) * x); unsigned int b = unsigned(float((color >> 16) & 0xff) * x); return (0xff000000 | b | g << 8 | r << 16); } void DrawTexture( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { const float quadUVs[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f }; const float quadVertices[] = { -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, -1.0f, -1.0f, 0.0f }; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, 0); + Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 1); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1); - deviceCommandContext->SetVertexBufferData(0, quadVertices); - deviceCommandContext->SetVertexBufferData(1, quadUVs); + deviceCommandContext->SetVertexBufferData( + 0, quadVertices, std::size(quadVertices) * sizeof(quadVertices[0])); + deviceCommandContext->SetVertexBufferData( + 1, quadUVs, std::size(quadUVs) * sizeof(quadUVs[0])); deviceCommandContext->Draw(0, 6); } struct MinimapUnitVertex { // This struct is copyable for convenience and because to move is to copy for primitives. u8 r, g, b, a; CVector2D position; }; // Adds a vertex to the passed VertexArray inline void AddEntity(const MinimapUnitVertex& v, VertexArrayIterator& attrColor, VertexArrayIterator& attrPos, - const float entityRadius) + const float entityRadius, + const bool useInstancing) { + if (useInstancing) + { + (*attrColor)[0] = v.r; + (*attrColor)[1] = v.g; + (*attrColor)[2] = v.b; + (*attrColor)[3] = v.a; + ++attrColor; + + (*attrPos)[0] = v.position.X; + (*attrPos)[1] = v.position.Y; + ++attrPos; + + return; + } + const CVector2D offsets[4] = { {-entityRadius, 0.0f}, {0.0f, -entityRadius}, {entityRadius, 0.0f}, {0.0f, entityRadius} }; for (const CVector2D& offset : offsets) { (*attrColor)[0] = v.r; (*attrColor)[1] = v.g; (*attrColor)[2] = v.b; (*attrColor)[3] = v.a; ++attrColor; (*attrPos)[0] = v.position.X + offset.X; (*attrPos)[1] = v.position.Y + offset.Y; ++attrPos; } } } // anonymous namespace CMiniMapTexture::CMiniMapTexture(CSimulation2& simulation) : m_Simulation(simulation), m_IndexArray(false), - m_VertexArray(Renderer::Backend::IBuffer::Type::VERTEX, true) + m_VertexArray(Renderer::Backend::IBuffer::Type::VERTEX, true), + m_InstanceVertexArray(Renderer::Backend::IBuffer::Type::VERTEX, false) { // Register Relax NG validator. CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng"); m_ShallowPassageHeight = GetShallowPassageHeight(); double blinkDuration = 1.0; // Tests won't have config initialised if (CConfigDB::IsInitialised()) { CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration); CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration); } m_HalfBlinkDuration = blinkDuration / 2.0; m_AttributePos.format = Renderer::Backend::Format::R32G32_SFLOAT; m_VertexArray.AddAttribute(&m_AttributePos); m_AttributeColor.format = Renderer::Backend::Format::R8G8B8A8_UNORM; m_VertexArray.AddAttribute(&m_AttributeColor); m_VertexArray.SetNumberOfVertices(MAX_ENTITIES_DRAWN * 4); m_VertexArray.Layout(); m_IndexArray.SetNumberOfVertices(MAX_ENTITIES_DRAWN * 6); m_IndexArray.Layout(); VertexArrayIterator index = m_IndexArray.GetIterator(); for (size_t i = 0; i < m_IndexArray.GetNumberOfVertices(); ++i) *index++ = 0; m_IndexArray.Upload(); VertexArrayIterator attrPos = m_AttributePos.GetIterator(); VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); for (size_t i = 0; i < m_VertexArray.GetNumberOfVertices(); ++i) { (*attrColor)[0] = 0; (*attrColor)[1] = 0; (*attrColor)[2] = 0; (*attrColor)[3] = 0; ++attrColor; (*attrPos)[0] = -10000.0f; (*attrPos)[1] = -10000.0f; ++attrPos; } m_VertexArray.Upload(); + + if (g_VideoMode.GetBackendDevice()->GetCapabilities().instancing) + { + m_UseInstancing = true; + + const size_t numberOfCircleSegments = 8; + + m_InstanceAttributePosition.format = Renderer::Backend::Format::R32G32_SFLOAT; + m_InstanceVertexArray.AddAttribute(&m_InstanceAttributePosition); + + m_InstanceVertexArray.SetNumberOfVertices(numberOfCircleSegments * 3); + m_InstanceVertexArray.Layout(); + + VertexArrayIterator attributePosition = + m_InstanceAttributePosition.GetIterator(); + for (size_t segment = 0; segment < numberOfCircleSegments; ++segment) + { + const float currentAngle = static_cast(segment) / numberOfCircleSegments * 2.0f * M_PI; + const float nextAngle = static_cast(segment + 1) / numberOfCircleSegments * 2.0f * M_PI; + + (*attributePosition)[0] = 0.0f; + (*attributePosition)[1] = 0.0f; + ++attributePosition; + + (*attributePosition)[0] = std::cos(currentAngle); + (*attributePosition)[1] = std::sin(currentAngle); + ++attributePosition; + + (*attributePosition)[0] = std::cos(nextAngle); + (*attributePosition)[1] = std::sin(nextAngle); + ++attributePosition; + } + + m_InstanceVertexArray.Upload(); + m_InstanceVertexArray.FreeBackingStore(); + } } CMiniMapTexture::~CMiniMapTexture() { DestroyTextures(); } void CMiniMapTexture::Update(const float UNUSED(deltaRealTime)) { if (m_WaterHeight != g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight) { m_TerrainTextureDirty = true; m_FinalTextureDirty = true; } } void CMiniMapTexture::Render(Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { const CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); if (!terrain) return; if (!m_TerrainTexture) CreateTextures(deviceCommandContext, terrain); if (m_TerrainTextureDirty) RebuildTerrainTexture(deviceCommandContext, terrain); RenderFinalTexture(deviceCommandContext); } void CMiniMapTexture::CreateTextures( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CTerrain* terrain) { DestroyTextures(); m_MapSize = terrain->GetVerticesPerSide(); const size_t textureSize = round_up_to_pow2(static_cast(m_MapSize)); const Renderer::Backend::Sampler::Desc defaultSamplerDesc = Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE); Renderer::Backend::IDevice* backendDevice = deviceCommandContext->GetDevice(); // Create terrain texture m_TerrainTexture = backendDevice->CreateTexture2D("MiniMapTerrainTexture", Renderer::Backend::Format::R8G8B8A8_UNORM, textureSize, textureSize, defaultSamplerDesc); // Initialise texture with solid black, for the areas we don't // overwrite with uploading later. std::unique_ptr texData = std::make_unique(textureSize * textureSize); for (size_t i = 0; i < textureSize * textureSize; ++i) texData[i] = 0xFF000000; deviceCommandContext->UploadTexture( m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, texData.get(), textureSize * textureSize * 4); texData.reset(); m_TerrainData = std::make_unique((m_MapSize - 1) * (m_MapSize - 1)); m_FinalTexture = backendDevice->CreateTexture2D("MiniMapFinalTexture", Renderer::Backend::Format::R8G8B8A8_UNORM, FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE, defaultSamplerDesc); m_FinalTextureFramebuffer = backendDevice->CreateFramebuffer("MiniMapFinalFramebuffer", m_FinalTexture.get(), nullptr); ENSURE(m_FinalTextureFramebuffer); } void CMiniMapTexture::DestroyTextures() { m_TerrainTexture.reset(); m_FinalTexture.reset(); m_TerrainData.reset(); } void CMiniMapTexture::RebuildTerrainTexture( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CTerrain* terrain) { const u32 x = 0; const u32 y = 0; const u32 width = m_MapSize - 1; const u32 height = m_MapSize - 1; m_WaterHeight = g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight; m_TerrainTextureDirty = false; for (u32 j = 0; j < height; ++j) { u32* dataPtr = m_TerrainData.get() + ((y + j) * width) + x; for (u32 i = 0; i < width; ++i) { const float avgHeight = ( terrain->GetVertexGroundLevel((int)i, (int)j) + terrain->GetVertexGroundLevel((int)i+1, (int)j) + terrain->GetVertexGroundLevel((int)i, (int)j+1) + terrain->GetVertexGroundLevel((int)i+1, (int)j+1) ) / 4.0f; if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight) { // shallow water *dataPtr++ = 0xffc09870; } else if (avgHeight < m_WaterHeight) { // Set water as constant color for consistency on different maps *dataPtr++ = 0xffa07850; } else { int hmap = ((int)terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8; int val = (hmap / 3) + 170; u32 color = 0xFFFFFFFF; CMiniPatch* mp = terrain->GetTile(x + i, y + j); if (mp) { CTerrainTextureEntry* tex = mp->GetTextureEntry(); if (tex) { // If the texture can't be loaded yet, set the dirty flags // so we'll try regenerating the terrain texture again soon if (!tex->GetTexture()->TryLoad()) m_TerrainTextureDirty = true; color = tex->GetBaseColor(); } } *dataPtr++ = ScaleColor(color, float(val) / 255.0f); } } } // Upload the texture deviceCommandContext->UploadTextureRegion( m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, m_TerrainData.get(), width * height * 4, 0, 0, width, height); } void CMiniMapTexture::RenderFinalTexture( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { // only update 2x / second // (note: since units only move a few pixels per second on the minimap, // we can get away with infrequent updates; this is slow) // TODO: Update all but camera at same speed as simulation const double currentTime = timer_Time(); const bool doUpdate = (currentTime - m_LastFinalTextureUpdate > 0.5) || m_FinalTextureDirty; if (doUpdate) m_LastFinalTextureUpdate = currentTime; else return; m_FinalTextureDirty = false; GPU_SCOPED_LABEL(deviceCommandContext, "Render minimap texture"); deviceCommandContext->SetFramebuffer(m_FinalTextureFramebuffer.get()); const SViewPort oldViewPort = g_Renderer.GetViewport(); const SViewPort viewPort = { 0, 0, FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE }; g_Renderer.SetViewport(viewPort); CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); ENSURE(cmpRangeManager); CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture(); const float invTileMapSize = 1.0f / static_cast(TERRAIN_TILE_SIZE * m_MapSize); const float texCoordMax = m_TerrainTexture ? static_cast(m_MapSize - 1) / m_TerrainTexture->GetWidth() : 1.0f; Renderer::Backend::IShaderProgram* shader = nullptr; CShaderTechniquePtr tech; CShaderDefines baseDefines; baseDefines.Add(str_MINIMAP_BASE, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, baseDefines); Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = tech->GetGraphicsPipelineStateDesc(); deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); shader = tech->GetShader(); if (m_TerrainTexture) { deviceCommandContext->SetTexture( shader->GetBindingSlot(str_baseTex), m_TerrainTexture.get()); } CMatrix3D baseTransform; baseTransform.SetIdentity(); CMatrix3D baseTextureTransform; baseTextureTransform.SetIdentity(); CMatrix3D terrainTransform; terrainTransform.SetIdentity(); terrainTransform.Scale(texCoordMax, texCoordMax, 1.0f); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), baseTransform.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_textureTransform), terrainTransform.AsFloatArray()); if (m_TerrainTexture) DrawTexture(deviceCommandContext); deviceCommandContext->EndPass(); pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; pipelineStateDesc.blendState.colorWriteMask = Renderer::Backend::ColorWriteMask::RED | Renderer::Backend::ColorWriteMask::GREEN | Renderer::Backend::ColorWriteMask::BLUE; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); // Draw territory boundaries CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture(); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_baseTex), territoryTexture.GetTexture()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), baseTransform.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_textureTransform), territoryTexture.GetMinimapTextureMatrix().AsFloatArray()); DrawTexture(deviceCommandContext); deviceCommandContext->EndPass(); pipelineStateDesc.blendState.enabled = false; pipelineStateDesc.blendState.colorWriteMask = Renderer::Backend::ColorWriteMask::ALPHA; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_baseTex), losTexture.GetTexture()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), baseTransform.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_textureTransform), losTexture.GetMinimapTextureMatrix().AsFloatArray()); DrawTexture(deviceCommandContext); deviceCommandContext->EndPass(); - CShaderDefines pointDefines; - pointDefines.Add(str_MINIMAP_POINT, str_1); - tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, pointDefines); - deviceCommandContext->SetGraphicsPipelineState( - tech->GetGraphicsPipelineStateDesc()); - deviceCommandContext->BeginPass(); - shader = tech->GetShader(); - deviceCommandContext->SetUniform( - shader->GetBindingSlot(str_transform), baseTransform.AsFloatArray()); - - CMatrix3D unitMatrix; - unitMatrix.SetIdentity(); - // Convert world space coordinates into [0, 2]. - const float unitScale = invTileMapSize; - unitMatrix.Scale(unitScale * 2.0f, unitScale * 2.0f, 1.0f); - // Offset the coordinates to [-1, 1]. - unitMatrix.Translate(CVector3D(-1.0f, -1.0f, 0.0f)); - deviceCommandContext->SetUniform( - shader->GetBindingSlot(str_transform), unitMatrix.AsFloatArray()); + // We might scale entities properly in the vertex shader but it requires + // additional space in the vertex buffer. So we assume that we don't need + // to change an entity size so often. + // Radius with instancing is lower because an entity has a more round shape. + const float entityRadius = static_cast(m_MapSize) / 128.0f * (m_UseInstancing ? 5.0 : 6.0f); if (doUpdate) { m_Icons.clear(); CSimulation2::InterfaceList ents = m_Simulation.GetEntitiesWithInterface(IID_Minimap); VertexArrayIterator attrPos = m_AttributePos.GetIterator(); VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); m_EntitiesDrawn = 0; MinimapUnitVertex v; std::vector pingingVertices; pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2); - // We might scale entities properly in the vertex shader but it requires - // additional space in the vertex buffer. So we assume that we don't need - // to change an entity size so often. - const float entityRadius = static_cast(m_MapSize) / 128.0f * 6.0f; - if (currentTime > m_NextBlinkTime) { m_BlinkState = !m_BlinkState; m_NextBlinkTime = currentTime + m_HalfBlinkDuration; } bool iconsEnabled = false; CFG_GET_VAL("gui.session.minimap.icons.enabled", iconsEnabled); float iconsOpacity = 1.0f; CFG_GET_VAL("gui.session.minimap.icons.opacity", iconsOpacity); float iconsSizeScale = 1.0f; CFG_GET_VAL("gui.session.minimap.icons.sizescale", iconsSizeScale); bool iconsCountOverflow = false; entity_pos_t posX, posZ; for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) { ICmpMinimap* cmpMinimap = static_cast(it->second); if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ)) { LosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, m_Simulation.GetSimContext().GetCurrentDisplayedPlayer()); if (vis != LosVisibility::HIDDEN) { v.a = 255; v.position.X = posX.ToFloat(); v.position.Y = posZ.ToFloat(); // Check minimap pinging to indicate something if (m_BlinkState && cmpMinimap->CheckPing(currentTime, m_PingDuration)) { v.r = 255; // ping color is white v.g = 255; v.b = 255; pingingVertices.push_back(v); } else { - AddEntity(v, attrColor, attrPos, entityRadius); + AddEntity(v, attrColor, attrPos, entityRadius, m_UseInstancing); ++m_EntitiesDrawn; } if (!iconsEnabled || !cmpMinimap->HasIcon()) continue; if (m_Icons.size() < MAX_ICON_COUNT) { CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture( CTextureProperties(cmpMinimap->GetIconPath())); const CColor color(v.r / 255.0f, v.g / 255.0f, v.b / 255.0f, iconsOpacity); m_Icons.emplace_back(Icon{ std::move(texture), color, v.position, cmpMinimap->GetIconSize() * iconsSizeScale * 0.5f}); } else { iconsCountOverflow = true; } } } } if (iconsCountOverflow) LOGWARNING("Too many minimap icons to draw: %zu/%zu", m_Icons.size(), MAX_ICON_COUNT); // Add the pinged vertices at the end, so they are drawn on top for (const MinimapUnitVertex& vertex : pingingVertices) { - AddEntity(vertex, attrColor, attrPos, entityRadius); + AddEntity(vertex, attrColor, attrPos, entityRadius, m_UseInstancing); ++m_EntitiesDrawn; } ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN); - VertexArrayIterator index = m_IndexArray.GetIterator(); - for (size_t entityIndex = 0; entityIndex < m_EntitiesDrawn; ++entityIndex) + if (!m_UseInstancing) { - index[entityIndex * 6 + 0] = static_cast(entityIndex * 4 + 0); - index[entityIndex * 6 + 1] = static_cast(entityIndex * 4 + 1); - index[entityIndex * 6 + 2] = static_cast(entityIndex * 4 + 2); - index[entityIndex * 6 + 3] = static_cast(entityIndex * 4 + 0); - index[entityIndex * 6 + 4] = static_cast(entityIndex * 4 + 2); - index[entityIndex * 6 + 5] = static_cast(entityIndex * 4 + 3); + VertexArrayIterator index = m_IndexArray.GetIterator(); + for (size_t entityIndex = 0; entityIndex < m_EntitiesDrawn; ++entityIndex) + { + index[entityIndex * 6 + 0] = static_cast(entityIndex * 4 + 0); + index[entityIndex * 6 + 1] = static_cast(entityIndex * 4 + 1); + index[entityIndex * 6 + 2] = static_cast(entityIndex * 4 + 2); + index[entityIndex * 6 + 3] = static_cast(entityIndex * 4 + 0); + index[entityIndex * 6 + 4] = static_cast(entityIndex * 4 + 2); + index[entityIndex * 6 + 5] = static_cast(entityIndex * 4 + 3); + } + + m_IndexArray.Upload(); } m_VertexArray.Upload(); - m_IndexArray.Upload(); } m_VertexArray.PrepareForRendering(); if (m_EntitiesDrawn > 0) { + CShaderDefines pointDefines; + pointDefines.Add(str_MINIMAP_POINT, str_1); + if (m_UseInstancing) + pointDefines.Add(str_USE_GPU_INSTANCING, str_1); + tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, pointDefines); + deviceCommandContext->SetGraphicsPipelineState( + tech->GetGraphicsPipelineStateDesc()); + deviceCommandContext->BeginPass(); + shader = tech->GetShader(); + deviceCommandContext->SetUniform( + shader->GetBindingSlot(str_transform), baseTransform.AsFloatArray()); + + CMatrix3D unitMatrix; + unitMatrix.SetIdentity(); + // Convert world space coordinates into [0, 2]. + const float unitScale = invTileMapSize; + unitMatrix.Scale(unitScale * 2.0f, unitScale * 2.0f, 1.0f); + // Offset the coordinates to [-1, 1]. + unitMatrix.Translate(CVector3D(-1.0f, -1.0f, 0.0f)); + deviceCommandContext->SetUniform( + shader->GetBindingSlot(str_transform), unitMatrix.AsFloatArray()); + Renderer::Backend::IDeviceCommandContext::Rect scissorRect; scissorRect.x = scissorRect.y = 1; scissorRect.width = scissorRect.height = FINAL_TEXTURE_SIZE - 2; deviceCommandContext->SetScissors(1, &scissorRect); m_VertexArray.UploadIfNeeded(deviceCommandContext); - m_IndexArray.UploadIfNeeded(deviceCommandContext); const uint32_t stride = m_VertexArray.GetStride(); const uint32_t firstVertexOffset = m_VertexArray.GetOffset() * stride; - deviceCommandContext->SetVertexAttributeFormat( - Renderer::Backend::VertexAttributeStream::POSITION, - m_AttributePos.format, firstVertexOffset + m_AttributePos.offset, stride, 0); - deviceCommandContext->SetVertexAttributeFormat( - Renderer::Backend::VertexAttributeStream::COLOR, - m_AttributeColor.format, firstVertexOffset + m_AttributeColor.offset, stride, 0); + if (m_UseInstancing) + { + deviceCommandContext->SetVertexAttributeFormat( + Renderer::Backend::VertexAttributeStream::POSITION, + m_AttributePos.format, + m_InstanceVertexArray.GetOffset() + m_InstanceAttributePosition.offset, + m_InstanceVertexArray.GetStride(), + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); + + deviceCommandContext->SetVertexAttributeFormat( + Renderer::Backend::VertexAttributeStream::UV1, + m_AttributePos.format, firstVertexOffset + m_AttributePos.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_INSTANCE, 1); + deviceCommandContext->SetVertexAttributeFormat( + Renderer::Backend::VertexAttributeStream::COLOR, + m_AttributeColor.format, firstVertexOffset + m_AttributeColor.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_INSTANCE, 1); + + deviceCommandContext->SetVertexBuffer(0, m_InstanceVertexArray.GetBuffer()); + deviceCommandContext->SetVertexBuffer(1, m_VertexArray.GetBuffer()); - deviceCommandContext->SetVertexBuffer(0, m_VertexArray.GetBuffer()); - deviceCommandContext->SetIndexBuffer(m_IndexArray.GetBuffer()); + deviceCommandContext->SetUniform(shader->GetBindingSlot(str_width), entityRadius); + + deviceCommandContext->DrawInstanced(0, m_InstanceVertexArray.GetNumberOfVertices(), 0, m_EntitiesDrawn); + } + else + { + m_IndexArray.UploadIfNeeded(deviceCommandContext); - deviceCommandContext->DrawIndexed(m_IndexArray.GetOffset(), m_EntitiesDrawn * 6, 0); + deviceCommandContext->SetVertexAttributeFormat( + Renderer::Backend::VertexAttributeStream::POSITION, + m_AttributePos.format, firstVertexOffset + m_AttributePos.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); + deviceCommandContext->SetVertexAttributeFormat( + Renderer::Backend::VertexAttributeStream::COLOR, + m_AttributeColor.format, firstVertexOffset + m_AttributeColor.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); + + deviceCommandContext->SetVertexBuffer(0, m_VertexArray.GetBuffer()); + deviceCommandContext->SetIndexBuffer(m_IndexArray.GetBuffer()); + + deviceCommandContext->DrawIndexed(m_IndexArray.GetOffset(), m_EntitiesDrawn * 6, 0); + } g_Renderer.GetStats().m_DrawCalls++; deviceCommandContext->SetScissors(0, nullptr); + + deviceCommandContext->EndPass(); } - deviceCommandContext->EndPass(); deviceCommandContext->SetFramebuffer( deviceCommandContext->GetDevice()->GetCurrentBackbuffer()); g_Renderer.SetViewport(oldViewPort); } // static float CMiniMapTexture::GetShallowPassageHeight() { float shallowPassageHeight = 0.0f; CParamNode externalParamNode; CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses"); if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk()) shallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat(); return shallowPassageHeight; } Index: ps/trunk/source/graphics/MiniMapTexture.h =================================================================== --- ps/trunk/source/graphics/MiniMapTexture.h (revision 26900) +++ ps/trunk/source/graphics/MiniMapTexture.h (revision 26901) @@ -1,117 +1,122 @@ /* 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_MINIMAPTEXTURE #define INCLUDED_MINIMAPTEXTURE #include "graphics/Color.h" #include "graphics/Texture.h" #include "maths/Vector2D.h" #include "renderer/backend/IDeviceCommandContext.h" #include "renderer/backend/ITexture.h" #include "renderer/VertexArray.h" #include #include class CSimulation2; class CTerrain; class CMiniMapTexture { NONCOPYABLE(CMiniMapTexture); public: CMiniMapTexture(CSimulation2& simulation); ~CMiniMapTexture(); /** * Marks the texture as dirty if it's old enough to redraw it on Render. */ void Update(const float deltaRealTime); /** * Redraws the texture if it's dirty. */ void Render(Renderer::Backend::IDeviceCommandContext* deviceCommandContext); Renderer::Backend::ITexture* GetTexture() const { return m_FinalTexture.get(); } /** * @return The maximum height for unit passage in water. */ static float GetShallowPassageHeight(); struct Icon { CTexturePtr texture; CColor color; CVector2D worldPosition; float halfSize; }; // Returns icons for corresponding entities on the minimap texture. const std::vector& GetIcons() { return m_Icons; } private: void CreateTextures( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CTerrain* terrain); void DestroyTextures(); void RebuildTerrainTexture( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CTerrain* terrain); void RenderFinalTexture( Renderer::Backend::IDeviceCommandContext* deviceCommandContext); CSimulation2& m_Simulation; bool m_TerrainTextureDirty = true; bool m_FinalTextureDirty = true; double m_LastFinalTextureUpdate = 0.0; // minimap texture handles std::unique_ptr m_TerrainTexture, m_FinalTexture; std::unique_ptr m_FinalTextureFramebuffer; // texture data std::unique_ptr m_TerrainData; // map size ssize_t m_MapSize = 0; // Maximal water height to allow the passage of a unit (for underwater shallows). float m_ShallowPassageHeight = 0.0f; float m_WaterHeight = 0.0f; VertexIndexArray m_IndexArray; VertexArray m_VertexArray; VertexArray::Attribute m_AttributePos; VertexArray::Attribute m_AttributeColor; + bool m_UseInstancing = false; + // Vertex data if instancing is supported. + VertexArray m_InstanceVertexArray; + VertexArray::Attribute m_InstanceAttributePosition; + size_t m_EntitiesDrawn = 0; double m_PingDuration = 25.0; double m_HalfBlinkDuration = 0.0; double m_NextBlinkTime = 0.0; bool m_BlinkState = false; std::vector m_Icons; }; #endif // INCLUDED_MINIMAPTEXTURE Index: ps/trunk/source/graphics/ParticleEmitter.cpp =================================================================== --- ps/trunk/source/graphics/ParticleEmitter.cpp (revision 26900) +++ ps/trunk/source/graphics/ParticleEmitter.cpp (revision 26901) @@ -1,313 +1,317 @@ /* 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 "ParticleEmitter.h" #include "graphics/LightEnv.h" #include "graphics/LOSTexture.h" #include "graphics/ParticleEmitterType.h" #include "graphics/ParticleManager.h" #include "graphics/ShaderProgram.h" #include "graphics/TextureManager.h" #include "ps/CStrInternStatic.h" #include "renderer/Renderer.h" #include "renderer/SceneRenderer.h" CParticleEmitter::CParticleEmitter(const CParticleEmitterTypePtr& type) : m_Type(type), m_Active(true), m_NextParticleIdx(0), m_EmissionRoundingError(0.f), m_LastUpdateTime(type->m_Manager.GetCurrentTime()), m_IndexArray(false), m_VertexArray(Renderer::Backend::IBuffer::Type::VERTEX, true), m_LastFrameNumber(-1) { // If we should start with particles fully emitted, pretend that we // were created in the past so the first update will produce lots of // particles. // TODO: instead of this, maybe it would make more sense to do a full // lifetime-length update of all emitters when the game first starts // (so that e.g. buildings constructed later on won't have fully-started // emitters, but those at the start will)? if (m_Type->m_StartFull) m_LastUpdateTime -= m_Type->m_MaxLifetime; m_Particles.reserve(m_Type->m_MaxParticles); m_AttributePos.format = Renderer::Backend::Format::R32G32B32_SFLOAT; m_VertexArray.AddAttribute(&m_AttributePos); m_AttributeAxis.format = Renderer::Backend::Format::R32G32_SFLOAT; m_VertexArray.AddAttribute(&m_AttributeAxis); m_AttributeUV.format = Renderer::Backend::Format::R32G32_SFLOAT; m_VertexArray.AddAttribute(&m_AttributeUV); m_AttributeColor.format = Renderer::Backend::Format::R8G8B8A8_UNORM; m_VertexArray.AddAttribute(&m_AttributeColor); m_VertexArray.SetNumberOfVertices(m_Type->m_MaxParticles * 4); m_VertexArray.Layout(); m_IndexArray.SetNumberOfVertices(m_Type->m_MaxParticles * 6); m_IndexArray.Layout(); VertexArrayIterator index = m_IndexArray.GetIterator(); for (u16 i = 0; i < m_Type->m_MaxParticles; ++i) { *index++ = i*4 + 0; *index++ = i*4 + 1; *index++ = i*4 + 2; *index++ = i*4 + 2; *index++ = i*4 + 3; *index++ = i*4 + 0; } m_IndexArray.Upload(); m_IndexArray.FreeBackingStore(); } void CParticleEmitter::UpdateArrayData(int frameNumber) { if (m_LastFrameNumber == frameNumber) return; m_LastFrameNumber = frameNumber; // Update m_Particles m_Type->UpdateEmitter(*this, m_Type->m_Manager.GetCurrentTime() - m_LastUpdateTime); m_LastUpdateTime = m_Type->m_Manager.GetCurrentTime(); // Regenerate the vertex array data: VertexArrayIterator attrPos = m_AttributePos.GetIterator(); VertexArrayIterator attrAxis = m_AttributeAxis.GetIterator(); VertexArrayIterator attrUV = m_AttributeUV.GetIterator(); VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); ENSURE(m_Particles.size() <= m_Type->m_MaxParticles); CBoundingBoxAligned bounds; for (size_t i = 0; i < m_Particles.size(); ++i) { // TODO: for more efficient rendering, maybe we should replace this with // a degenerate quad if alpha is 0 bounds += m_Particles[i].pos; *attrPos++ = m_Particles[i].pos; *attrPos++ = m_Particles[i].pos; *attrPos++ = m_Particles[i].pos; *attrPos++ = m_Particles[i].pos; // Compute corner offsets, split into sin/cos components so the vertex // shader can multiply by the camera-right (or left?) and camera-up vectors // to get rotating billboards: float s = sin(m_Particles[i].angle) * m_Particles[i].size/2.f; float c = cos(m_Particles[i].angle) * m_Particles[i].size/2.f; (*attrAxis)[0] = c; (*attrAxis)[1] = s; ++attrAxis; (*attrAxis)[0] = s; (*attrAxis)[1] = -c; ++attrAxis; (*attrAxis)[0] = -c; (*attrAxis)[1] = -s; ++attrAxis; (*attrAxis)[0] = -s; (*attrAxis)[1] = c; ++attrAxis; (*attrUV)[0] = 1; (*attrUV)[1] = 0; ++attrUV; (*attrUV)[0] = 0; (*attrUV)[1] = 0; ++attrUV; (*attrUV)[0] = 0; (*attrUV)[1] = 1; ++attrUV; (*attrUV)[0] = 1; (*attrUV)[1] = 1; ++attrUV; SColor4ub color = m_Particles[i].color; // Special case: If the blending depends on the source color, not the source alpha, // then pre-multiply by the alpha. (This is kind of a hack.) if (m_Type->m_BlendMode == CParticleEmitterType::BlendMode::OVERLAY || m_Type->m_BlendMode == CParticleEmitterType::BlendMode::MULTIPLY) { color.R = (color.R * color.A) / 255; color.G = (color.G * color.A) / 255; color.B = (color.B * color.A) / 255; } *attrColor++ = color; *attrColor++ = color; *attrColor++ = color; *attrColor++ = color; } m_ParticleBounds = bounds; m_VertexArray.Upload(); } void CParticleEmitter::PrepareForRendering() { m_VertexArray.PrepareForRendering(); } void CParticleEmitter::Bind( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IShaderProgram* shader) { m_Type->m_Texture->UploadBackendTextureIfNeeded(deviceCommandContext); CLOSTexture& los = g_Renderer.GetSceneRenderer().GetScene().GetLOSTexture(); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_losTex), los.GetTextureSmooth()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_losTransform), los.GetTextureMatrix()[0], los.GetTextureMatrix()[12]); const CLightEnv& lightEnv = g_Renderer.GetSceneRenderer().GetLightEnv(); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_sunColor), lightEnv.m_SunColor.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_fogColor), lightEnv.m_FogColor.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_fogParams), lightEnv.m_FogFactor, lightEnv.m_FogMax); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_baseTex), m_Type->m_Texture->GetBackendTexture()); } void CParticleEmitter::RenderArray( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { if (m_Particles.empty()) return; m_VertexArray.UploadIfNeeded(deviceCommandContext); m_IndexArray.UploadIfNeeded(deviceCommandContext); const uint32_t stride = m_VertexArray.GetStride(); const uint32_t firstVertexOffset = m_VertexArray.GetOffset() * stride; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - m_AttributePos.format, firstVertexOffset + m_AttributePos.offset, stride, 0); + m_AttributePos.format, firstVertexOffset + m_AttributePos.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::COLOR, - m_AttributeColor.format, firstVertexOffset + m_AttributeColor.offset, stride, 0); + m_AttributeColor.format, firstVertexOffset + m_AttributeColor.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, - m_AttributeUV.format, firstVertexOffset + m_AttributeUV.offset, stride, 0); + m_AttributeUV.format, firstVertexOffset + m_AttributeUV.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV1, - m_AttributeAxis.format, firstVertexOffset + m_AttributeAxis.offset, stride, 0); + m_AttributeAxis.format, firstVertexOffset + m_AttributeAxis.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexBuffer(0, m_VertexArray.GetBuffer()); deviceCommandContext->SetIndexBuffer(m_IndexArray.GetBuffer()); deviceCommandContext->DrawIndexed(m_IndexArray.GetOffset(), m_Particles.size() * 6, 0); g_Renderer.GetStats().m_DrawCalls++; g_Renderer.GetStats().m_Particles += m_Particles.size(); } void CParticleEmitter::Unattach(const CParticleEmitterPtr& self) { m_Active = false; m_Type->m_Manager.AddUnattachedEmitter(self); } void CParticleEmitter::AddParticle(const SParticle& particle) { if (m_NextParticleIdx >= m_Particles.size()) m_Particles.push_back(particle); else m_Particles[m_NextParticleIdx] = particle; m_NextParticleIdx = (m_NextParticleIdx + 1) % m_Type->m_MaxParticles; } void CParticleEmitter::SetEntityVariable(const std::string& name, float value) { m_EntityVariables[name] = value; } CModelParticleEmitter::CModelParticleEmitter(const CParticleEmitterTypePtr& type) : m_Type(type) { m_Emitter = CParticleEmitterPtr(new CParticleEmitter(m_Type)); } CModelParticleEmitter::~CModelParticleEmitter() { m_Emitter->Unattach(m_Emitter); } void CModelParticleEmitter::SetEntityVariable(const std::string& name, float value) { m_Emitter->SetEntityVariable(name, value); } CModelAbstract* CModelParticleEmitter::Clone() const { return new CModelParticleEmitter(m_Type); } void CModelParticleEmitter::CalcBounds() { // TODO: we ought to compute sensible bounds here, probably based on the // current computed particle positions plus the emitter type's largest // potential bounding box at the current position m_WorldBounds = m_Type->CalculateBounds(m_Emitter->GetPosition(), m_Emitter->GetParticleBounds()); } void CModelParticleEmitter::ValidatePosition() { // TODO: do we need to do anything here? // This is a convenient (though possibly not particularly appropriate) place // to invalidate bounds so they'll be recomputed from the recent particle data InvalidateBounds(); } void CModelParticleEmitter::InvalidatePosition() { } void CModelParticleEmitter::SetTransform(const CMatrix3D& transform) { if (m_Transform == transform) return; m_Emitter->SetPosition(transform.GetTranslation()); m_Emitter->SetRotation(transform.GetRotation()); // call base class to set transform on this object CRenderableObject::SetTransform(transform); } Index: ps/trunk/source/graphics/TextRenderer.cpp =================================================================== --- ps/trunk/source/graphics/TextRenderer.cpp (revision 26900) +++ ps/trunk/source/graphics/TextRenderer.cpp (revision 26901) @@ -1,348 +1,351 @@ /* 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 "TextRenderer.h" #include "graphics/Font.h" #include "graphics/FontManager.h" #include "graphics/ShaderProgram.h" #include "graphics/TextureManager.h" #include "maths/Matrix3D.h" #include "ps/CStrIntern.h" #include "ps/CStrInternStatic.h" #include "renderer/Renderer.h" #include namespace { // We can't draw chars more than vertices, currently we use 4 vertices per char. constexpr size_t MAX_CHAR_COUNT_PER_BATCH = 65536 / 4; } // anonymous namespace CTextRenderer::CTextRenderer() { ResetTranslate(); SetCurrentColor(CColor(1.0f, 1.0f, 1.0f, 1.0f)); SetCurrentFont(str_sans_10); } void CTextRenderer::ResetTranslate(const CVector2D& translate) { m_Translate = translate; m_Dirty = true; } void CTextRenderer::Translate(float x, float y) { m_Translate += CVector2D{x, y}; m_Dirty = true; } void CTextRenderer::SetClippingRect(const CRect& rect) { m_Clipping = rect; } void CTextRenderer::SetCurrentColor(const CColor& color) { if (m_Color != color) { m_Color = color; m_Dirty = true; } } void CTextRenderer::SetCurrentFont(CStrIntern font) { if (font != m_FontName) { m_FontName = font; m_Font = g_Renderer.GetFontManager().LoadFont(font); m_Dirty = true; } } void CTextRenderer::PrintfAdvance(const wchar_t* fmt, ...) { wchar_t buf[1024] = {0}; va_list args; va_start(args, fmt); int ret = vswprintf(buf, ARRAY_SIZE(buf)-1, fmt, args); va_end(args); if (ret < 0) debug_printf("CTextRenderer::Printf vswprintf failed (buffer size exceeded?) - return value %d, errno %d\n", ret, errno); PutAdvance(buf); } void CTextRenderer::PrintfAt(float x, float y, const wchar_t* fmt, ...) { wchar_t buf[1024] = {0}; va_list args; va_start(args, fmt); int ret = vswprintf(buf, ARRAY_SIZE(buf)-1, fmt, args); va_end(args); if (ret < 0) debug_printf("CTextRenderer::PrintfAt vswprintf failed (buffer size exceeded?) - return value %d, errno %d\n", ret, errno); Put(x, y, buf); } void CTextRenderer::PutAdvance(const wchar_t* buf) { Put(0.0f, 0.0f, buf); int w, h; m_Font->CalculateStringSize(buf, w, h); Translate((float)w, 0.0f); } void CTextRenderer::Put(float x, float y, const wchar_t* buf) { if (buf[0] == 0) return; // empty string; don't bother storing PutString(x, y, new std::wstring(buf), true); } void CTextRenderer::Put(float x, float y, const char* buf) { if (buf[0] == 0) return; // empty string; don't bother storing PutString(x, y, new std::wstring(wstring_from_utf8(buf)), true); } void CTextRenderer::Put(float x, float y, const std::wstring* buf) { if (buf->empty()) return; // empty string; don't bother storing PutString(x, y, buf, false); } void CTextRenderer::PutString(float x, float y, const std::wstring* buf, bool owned) { if (!m_Font) return; // invalid font; can't render if (m_Clipping != CRect()) { float x0, y0, x1, y1; m_Font->GetGlyphBounds(x0, y0, x1, y1); if (y + y1 < m_Clipping.top) return; if (y + y0 > m_Clipping.bottom) return; } // If any state has changed since the last batch, start a new batch if (m_Dirty) { SBatch batch; batch.chars = 0; batch.translate = m_Translate; batch.color = m_Color; batch.font = m_Font; m_Batches.push_back(batch); m_Dirty = false; } // Push a new run onto the latest batch SBatchRun run; run.x = x; run.y = y; m_Batches.back().runs.push_back(run); m_Batches.back().runs.back().text = buf; m_Batches.back().runs.back().owned = owned; m_Batches.back().chars += buf->size(); } struct t2f_v2i { t2f_v2i() : u(0), v(0), x(0), y(0) { } float u, v; i16 x, y; }; struct SBatchCompare { bool operator()(const CTextRenderer::SBatch& a, const CTextRenderer::SBatch& b) { if (a.font != b.font) return a.font < b.font; // TODO: is it worth sorting by color/translate too? return false; } }; void CTextRenderer::Render( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IShaderProgram* shader, const CMatrix3D& transform) { std::vector indexes; std::vector vertexes; // Try to merge non-consecutive batches that share the same font/color/translate: // sort the batch list by font, then merge the runs of adjacent compatible batches m_Batches.sort(SBatchCompare()); for (std::list::iterator it = m_Batches.begin(); it != m_Batches.end(); ) { std::list::iterator next = std::next(it); if (next != m_Batches.end() && it->chars + next->chars <= MAX_CHAR_COUNT_PER_BATCH && it->font == next->font && it->color == next->color && it->translate == next->translate) { it->chars += next->chars; it->runs.splice(it->runs.end(), next->runs); m_Batches.erase(next); } else ++it; } const int32_t texBindingSlot = shader->GetBindingSlot(str_tex); const int32_t transformBindingSlot = shader->GetBindingSlot(str_transform); const int32_t colorAddBindingSlot = shader->GetBindingSlot(str_colorAdd); const int32_t colorMulBindingSlot = shader->GetBindingSlot(str_colorMul); bool transformChanged = false; CTexture* lastTexture = nullptr; for (std::list::iterator it = m_Batches.begin(); it != m_Batches.end(); ++it) { SBatch& batch = *it; const CFont::GlyphMap& glyphs = batch.font->GetGlyphs(); if (lastTexture != batch.font->GetTexture().get()) { lastTexture = batch.font->GetTexture().get(); lastTexture->UploadBackendTextureIfNeeded(deviceCommandContext); deviceCommandContext->SetTexture(texBindingSlot, lastTexture->GetBackendTexture()); } if (batch.translate.X != 0.0f || batch.translate.Y != 0.0f) { CMatrix3D localTransform; localTransform.SetTranslation(batch.translate.X, batch.translate.Y, 0.0f); localTransform = transform * localTransform; deviceCommandContext->SetUniform(transformBindingSlot, localTransform.AsFloatArray()); transformChanged = true; } // ALPHA-only textures will have .rgb sampled as 0, so we need to // replace it with white (but not affect RGBA textures) if (batch.font->HasRGB()) deviceCommandContext->SetUniform(colorAddBindingSlot, 0.0f, 0.0f, 0.0f, 0.0f); else deviceCommandContext->SetUniform(colorAddBindingSlot, batch.color.r, batch.color.g, batch.color.b, 0.0f); deviceCommandContext->SetUniform(colorMulBindingSlot, batch.color.AsFloatArray()); vertexes.resize(std::min(MAX_CHAR_COUNT_PER_BATCH, batch.chars) * 4); indexes.resize(std::min(MAX_CHAR_COUNT_PER_BATCH, batch.chars) * 6); size_t idx = 0; auto flush = [deviceCommandContext, &idx, &vertexes, &indexes]() -> void { if (idx == 0) return; const uint32_t stride = sizeof(t2f_v2i); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R16G16_SINT, offsetof(t2f_v2i, x), stride, 0); + Renderer::Backend::Format::R16G16_SINT, offsetof(t2f_v2i, x), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, - Renderer::Backend::Format::R32G32_SFLOAT, offsetof(t2f_v2i, u), stride, 0); + Renderer::Backend::Format::R32G32_SFLOAT, offsetof(t2f_v2i, u), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); - deviceCommandContext->SetVertexBufferData(0, vertexes.data()); - deviceCommandContext->SetIndexBufferData(indexes.data()); + deviceCommandContext->SetVertexBufferData( + 0, vertexes.data(), vertexes.size() * sizeof(vertexes[0])); + deviceCommandContext->SetIndexBufferData(indexes.data(), indexes.size() * sizeof(indexes[0])); deviceCommandContext->DrawIndexed(0, idx * 6, 0); idx = 0; }; for (std::list::iterator runit = batch.runs.begin(); runit != batch.runs.end(); ++runit) { SBatchRun& run = *runit; i16 x = run.x; i16 y = run.y; for (size_t i = 0; i < run.text->size(); ++i) { const CFont::GlyphData* g = glyphs.get((*run.text)[i]); if (!g) g = glyphs.get(0xFFFD); // Use the missing glyph symbol if (!g) // Missing the missing glyph symbol - give up continue; vertexes[idx*4].u = g->u1; vertexes[idx*4].v = g->v0; vertexes[idx*4].x = g->x1 + x; vertexes[idx*4].y = g->y0 + y; vertexes[idx*4+1].u = g->u0; vertexes[idx*4+1].v = g->v0; vertexes[idx*4+1].x = g->x0 + x; vertexes[idx*4+1].y = g->y0 + y; vertexes[idx*4+2].u = g->u0; vertexes[idx*4+2].v = g->v1; vertexes[idx*4+2].x = g->x0 + x; vertexes[idx*4+2].y = g->y1 + y; vertexes[idx*4+3].u = g->u1; vertexes[idx*4+3].v = g->v1; vertexes[idx*4+3].x = g->x1 + x; vertexes[idx*4+3].y = g->y1 + y; indexes[idx*6+0] = static_cast(idx*4+0); indexes[idx*6+1] = static_cast(idx*4+1); indexes[idx*6+2] = static_cast(idx*4+2); indexes[idx*6+3] = static_cast(idx*4+2); indexes[idx*6+4] = static_cast(idx*4+3); indexes[idx*6+5] = static_cast(idx*4+0); x += g->xadvance; ++idx; if (idx == MAX_CHAR_COUNT_PER_BATCH) flush(); } } flush(); } m_Batches.clear(); if (transformChanged) deviceCommandContext->SetUniform(transformBindingSlot, transform.AsFloatArray()); } Index: ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp (revision 26900) +++ ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp (revision 26901) @@ -1,494 +1,498 @@ /* 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 "CMiniMap.h" #include "graphics/Canvas2D.h" #include "graphics/GameView.h" #include "graphics/LOSTexture.h" #include "graphics/MiniMapTexture.h" #include "graphics/MiniPatch.h" #include "graphics/ShaderManager.h" #include "graphics/ShaderProgramPtr.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TextureManager.h" #include "gui/CGUI.h" #include "gui/GUIManager.h" #include "gui/GUIMatrix.h" #include "lib/bits.h" #include "lib/external_libraries/libsdl.h" #include "lib/timer.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/GameSetup/Config.h" #include "ps/Profile.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/SceneRenderer.h" #include "renderer/WaterManager.h" #include "scriptinterface/Object.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpMinimap.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/helpers/Los.h" #include "simulation2/system/ParamNode.h" #include #include #include namespace { // Adds segments pieces lying inside the circle to lines. void CropPointsByCircle(const std::array& points, const CVector3D& center, const float radius, std::vector* lines) { constexpr float EPS = 1e-3f; lines->reserve(points.size() * 2); for (size_t idx = 0; idx < points.size(); ++idx) { const CVector3D& currentPoint = points[idx]; const CVector3D& nextPoint = points[(idx + 1) % points.size()]; const CVector3D direction = (nextPoint - currentPoint).Normalized(); const CVector3D normal(direction.Z, 0.0f, -direction.X); const float offset = normal.Dot(currentPoint) - normal.Dot(center); // We need to have lines only inside the circle. if (std::abs(offset) + EPS >= radius) continue; const CVector3D closestPoint = center + normal * offset; const float halfChordLength = sqrt(radius * radius - offset * offset); const CVector3D intersectionA = closestPoint - direction * halfChordLength; const CVector3D intersectionB = closestPoint + direction * halfChordLength; // We have no intersection if the segment is lying outside of the circle. if (direction.Dot(currentPoint) + EPS > direction.Dot(intersectionB) || direction.Dot(nextPoint) - EPS < direction.Dot(intersectionA)) continue; lines->emplace_back( direction.Dot(currentPoint) > direction.Dot(intersectionA) ? currentPoint : intersectionA); lines->emplace_back( direction.Dot(nextPoint) < direction.Dot(intersectionB) ? nextPoint : intersectionB); } } void DrawTexture( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, float angle, float x, float y, float x2, float y2, float mapScale) { // Rotate the texture coordinates (0,0)-(coordMax,coordMax) around their center point (m,m) // Scale square maps to fit in circular minimap area const float s = sin(angle) * mapScale; const float c = cos(angle) * mapScale; const float m = 0.5f; float quadTex[] = { m*(-c + s + 1.f), m*(-c + -s + 1.f), m*(c + s + 1.f), m*(-c + s + 1.f), m*(c + -s + 1.f), m*(c + s + 1.f), m*(c + -s + 1.f), m*(c + s + 1.f), m*(-c + -s + 1.f), m*(c + -s + 1.f), m*(-c + s + 1.f), m*(-c + -s + 1.f) }; float quadVerts[] = { x, y, 0.0f, x2, y, 0.0f, x2, y2, 0.0f, x2, y2, 0.0f, x, y2, 0.0f, x, y, 0.0f }; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, 0); + Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 1); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1); - deviceCommandContext->SetVertexBufferData(0, quadVerts); - deviceCommandContext->SetVertexBufferData(1, quadTex); + deviceCommandContext->SetVertexBufferData( + 0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0])); + deviceCommandContext->SetVertexBufferData( + 1, quadTex, std::size(quadTex) * sizeof(quadTex[0])); deviceCommandContext->Draw(0, 6); } } // anonymous namespace const CStr CMiniMap::EventNameWorldClick = "WorldClick"; CMiniMap::CMiniMap(CGUI& pGUI) : IGUIObject(pGUI), m_MapSize(0), m_MapScale(1.f), m_Mask(this, "mask", false), m_FlareTextureCount(this, "flare_texture_count", 0), m_FlareRenderSize(this, "flare_render_size", 0), m_FlareInterleave(this, "flare_interleave", false), m_FlareAnimationSpeed(this, "flare_animation_speed", 0.0f), m_FlareLifetimeSeconds(this, "flare_lifetime_seconds", 0.0f), m_FlareStartFadeSeconds(this, "flare_start_fade_seconds", 0.0f), m_FlareStopFadeSeconds(this, "flare_stop_fade_seconds", 0.0f) { m_Clicking = false; m_MouseHovering = false; } CMiniMap::~CMiniMap() = default; void CMiniMap::HandleMessage(SGUIMessage& Message) { IGUIObject::HandleMessage(Message); switch (Message.type) { case GUIM_LOAD: RecreateFlareTextures(); break; case GUIM_SETTINGS_UPDATED: if (Message.value == "flare_texture_count") RecreateFlareTextures(); break; case GUIM_MOUSE_PRESS_LEFT: if (m_MouseHovering) { if (!CMiniMap::FireWorldClickEvent(SDL_BUTTON_LEFT, 1)) { SetCameraPositionFromMousePosition(); m_Clicking = true; } } break; case GUIM_MOUSE_RELEASE_LEFT: if (m_MouseHovering && m_Clicking) SetCameraPositionFromMousePosition(); m_Clicking = false; break; case GUIM_MOUSE_DBLCLICK_LEFT: if (m_MouseHovering && m_Clicking) SetCameraPositionFromMousePosition(); m_Clicking = false; break; case GUIM_MOUSE_ENTER: m_MouseHovering = true; break; case GUIM_MOUSE_LEAVE: m_Clicking = false; m_MouseHovering = false; break; case GUIM_MOUSE_RELEASE_RIGHT: CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 1); break; case GUIM_MOUSE_DBLCLICK_RIGHT: CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 2); break; case GUIM_MOUSE_MOTION: if (m_MouseHovering && m_Clicking) SetCameraPositionFromMousePosition(); break; case GUIM_MOUSE_WHEEL_DOWN: case GUIM_MOUSE_WHEEL_UP: Message.Skip(); break; default: break; } } void CMiniMap::RecreateFlareTextures() { // Catch invalid values. if (m_FlareTextureCount > 99) { LOGERROR("Invalid value for flare texture count. Valid range is 0-99."); return; } const CStr textureNumberingFormat = "art/textures/animated/minimap-flare/frame%02u.png"; m_FlareTextures.clear(); m_FlareTextures.reserve(m_FlareTextureCount); for (u32 i = 0; i < m_FlareTextureCount; ++i) { CTextureProperties textureProps(fmt::sprintf(textureNumberingFormat, i).c_str()); textureProps.SetIgnoreQuality(true); m_FlareTextures.emplace_back(g_Renderer.GetTextureManager().CreateTexture(textureProps)); } } bool CMiniMap::IsMouseOver() const { const CVector2D& mousePos = m_pGUI.GetMousePos(); // Take the magnitude of the difference of the mouse position and minimap center. const float distanceFromCenter = (mousePos - m_CachedActualSize.CenterPoint()).Length(); // If the distance is less then the radius of the minimap (half the width) the mouse is over the minimap. return distanceFromCenter < m_CachedActualSize.GetWidth() / 2.0; } void CMiniMap::GetMouseWorldCoordinates(float& x, float& z) const { // Determine X and Z according to proportion of mouse position and minimap. const CVector2D& mousePos = m_pGUI.GetMousePos(); float px = (mousePos.X - m_CachedActualSize.left) / m_CachedActualSize.GetWidth(); float py = (m_CachedActualSize.bottom - mousePos.Y) / m_CachedActualSize.GetHeight(); float angle = GetAngle(); // Scale world coordinates for shrunken square map x = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(px-0.5) - sin(angle)*(py-0.5)) + 0.5); z = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(py-0.5) + sin(angle)*(px-0.5)) + 0.5); } void CMiniMap::SetCameraPositionFromMousePosition() { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); CVector3D target; GetMouseWorldCoordinates(target.X, target.Z); target.Y = terrain->GetExactGroundLevel(target.X, target.Z); g_Game->GetView()->MoveCameraTarget(target); } float CMiniMap::GetAngle() const { CVector3D cameraIn = g_Game->GetView()->GetCamera()->GetOrientation().GetIn(); return -atan2(cameraIn.X, cameraIn.Z); } CVector2D CMiniMap::WorldSpaceToMiniMapSpace(const CVector3D& worldPosition) const { // Coordinates with 0,0 in the middle of the minimap and +-0.5 as max. const float invTileMapSize = 1.0f / static_cast(TERRAIN_TILE_SIZE * m_MapSize); const float relativeX = (worldPosition.X * invTileMapSize - 0.5) / m_MapScale; const float relativeY = (worldPosition.Z * invTileMapSize - 0.5) / m_MapScale; // Rotate coordinates. const float angle = GetAngle(); const float rotatedX = cos(angle) * relativeX + sin(angle) * relativeY; const float rotatedY = -sin(angle) * relativeX + cos(angle) * relativeY; // Calculate coordinates in GUI space. return CVector2D( m_CachedActualSize.left + (0.5f + rotatedX) * m_CachedActualSize.GetWidth(), m_CachedActualSize.bottom - (0.5f + rotatedY) * m_CachedActualSize.GetHeight()); } bool CMiniMap::FireWorldClickEvent(int button, int UNUSED(clicks)) { ScriptRequest rq(g_GUI->GetActiveGUI()->GetScriptInterface()); float x, z; GetMouseWorldCoordinates(x, z); JS::RootedValue coords(rq.cx); Script::CreateObject(rq, &coords, "x", x, "z", z); JS::RootedValue buttonJs(rq.cx); Script::ToJSVal(rq, &buttonJs, button); JS::RootedValueVector paramData(rq.cx); ignore_result(paramData.append(coords)); ignore_result(paramData.append(buttonJs)); return ScriptEventWithReturn(EventNameWorldClick, paramData); } // This sets up and draws the rectangle on the minimap // which represents the view of the camera in the world. void CMiniMap::DrawViewRect(CCanvas2D& canvas) const { // Compute the camera frustum intersected with a fixed-height plane. // Use the water height as a fixed base height, which should be the lowest we can go const float sampleHeight = g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight; const CCamera* camera = g_Game->GetView()->GetCamera(); const std::array hitPoints = { camera->GetWorldCoordinates(0, g_Renderer.GetHeight(), sampleHeight), camera->GetWorldCoordinates(g_Renderer.GetWidth(), g_Renderer.GetHeight(), sampleHeight), camera->GetWorldCoordinates(g_Renderer.GetWidth(), 0, sampleHeight), camera->GetWorldCoordinates(0, 0, sampleHeight) }; std::vector worldSpaceLines; // We need to prevent drawing view bounds out of the map. const float halfMapSize = static_cast((m_MapSize - 1) * TERRAIN_TILE_SIZE) * 0.5f; CropPointsByCircle(hitPoints, CVector3D(halfMapSize, 0.0f, halfMapSize), halfMapSize * m_MapScale, &worldSpaceLines); if (worldSpaceLines.empty()) return; for (size_t index = 0; index < worldSpaceLines.size() && index + 1 < worldSpaceLines.size(); index += 2) { const CVector2D from = WorldSpaceToMiniMapSpace(worldSpaceLines[index]); const CVector2D to = WorldSpaceToMiniMapSpace(worldSpaceLines[index + 1]); canvas.DrawLine({from, to}, 2.0f, CColor(1.0f, 0.3f, 0.3f, 1.0f)); } } void CMiniMap::DrawFlare(CCanvas2D& canvas, const MapFlare& flare, double currentTime) const { if (m_FlareTextures.empty()) return; const CVector2D flareCenter = WorldSpaceToMiniMapSpace(CVector3D(flare.pos.X, 0.0f, flare.pos.Y)); const CRect destination( flareCenter.X - m_FlareRenderSize, flareCenter.Y - m_FlareRenderSize, flareCenter.X + m_FlareRenderSize, flareCenter.Y + m_FlareRenderSize); const double deltaTime = currentTime - flare.time; const double remainingTime = m_FlareLifetimeSeconds - deltaTime; const u32 flooredStep = floor(deltaTime * m_FlareAnimationSpeed); const float startFadeAlpha = m_FlareStartFadeSeconds > 0.0f ? deltaTime / m_FlareStartFadeSeconds : 1.0f; const float stopFadeAlpha = m_FlareStopFadeSeconds > 0.0f ? remainingTime / m_FlareStopFadeSeconds : 1.0f; const float alpha = Clamp(std::min( SmoothStep(0.0f, 1.0f, startFadeAlpha), SmoothStep(0.0f, 1.0f, stopFadeAlpha)), 0.0f, 1.0f); DrawFlareFrame(canvas, flooredStep % m_FlareTextures.size(), destination, flare.color, alpha); // Draw a second circle if the first has reached half of the animation. if (m_FlareInterleave && flooredStep >= m_FlareTextures.size() / 2) { DrawFlareFrame(canvas, (flooredStep - m_FlareTextures.size() / 2) % m_FlareTextures.size(), destination, flare.color, alpha); } } void CMiniMap::DrawFlareFrame(CCanvas2D& canvas, const u32 frameIndex, const CRect& destination, const CColor& color, float alpha) const { // TODO: Only draw inside the minimap circle. CTexturePtr texture = m_FlareTextures[frameIndex % m_FlareTextures.size()]; CColor finalColor = color; finalColor.a *= alpha; canvas.DrawTexture(texture, destination, CRect(0, 0, texture->GetWidth(), texture->GetHeight()), finalColor, CColor(0.0f, 0.0f, 0.0f, 0.0f), 0.0f); } void CMiniMap::Draw(CCanvas2D& canvas) { PROFILE3("render minimap"); // The terrain isn't actually initialized until the map is loaded, which // happens when the game is started, so abort until then. if (!g_Game || !g_Game->IsGameStarted()) return; if (!m_Mask) canvas.DrawRect(m_CachedActualSize, CColor(0.0f, 0.0f, 0.0f, 1.0f)); canvas.Flush(); CSimulation2* sim = g_Game->GetSimulation2(); CmpPtr cmpRangeManager(*sim, SYSTEM_ENTITY); ENSURE(cmpRangeManager); // Set our globals in case they hadn't been set before const CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); m_MapSize = terrain->GetVerticesPerSide(); m_MapScale = (cmpRangeManager->GetLosCircular() ? 1.f : 1.414f); // Draw the main textured quad CMiniMapTexture& miniMapTexture = g_Game->GetView()->GetMiniMapTexture(); if (miniMapTexture.GetTexture()) { CShaderDefines baseDefines; baseDefines.Add(str_MINIMAP_BASE, str_1); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, baseDefines); Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = tech->GetGraphicsPipelineStateDesc(); pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; Renderer::Backend::IDeviceCommandContext* deviceCommandContext = g_Renderer.GetDeviceCommandContext(); deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = tech->GetShader(); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_baseTex), miniMapTexture.GetTexture()); const CMatrix3D baseTransform = GetDefaultGuiMatrix(); CMatrix3D baseTextureTransform; baseTextureTransform.SetIdentity(); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), baseTransform.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_textureTransform), baseTextureTransform.AsFloatArray()); const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom; const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top; const float angle = GetAngle(); DrawTexture(deviceCommandContext, angle, x, y, x2, y2, m_MapScale); deviceCommandContext->EndPass(); } for (const CMiniMapTexture::Icon& icon : miniMapTexture.GetIcons()) { const CVector2D center = WorldSpaceToMiniMapSpace( CVector3D(icon.worldPosition.X, 0.0f, icon.worldPosition.Y)); const CRect destination( center.X - icon.halfSize, center.Y - icon.halfSize, center.X + icon.halfSize, center.Y + icon.halfSize); const CRect source(0, 0, icon.texture->GetWidth(), icon.texture->GetHeight()); canvas.DrawTexture( icon.texture, destination, source, icon.color, CColor(0.0f, 0.0f, 0.0f, 0.0f), 0.0f); } PROFILE_START("minimap flares"); DrawViewRect(canvas); const double currentTime = timer_Time(); while (!m_MapFlares.empty() && m_FlareLifetimeSeconds + m_MapFlares.front().time < currentTime) m_MapFlares.pop_front(); for (const MapFlare& flare : m_MapFlares) DrawFlare(canvas, flare, currentTime); PROFILE_END("minimap flares"); } bool CMiniMap::Flare(const CVector2D& pos, const CStr& colorStr) { CColor color; if (!color.ParseString(colorStr)) { LOGERROR("CMiniMap::Flare: Couldn't parse color string"); return false; } m_MapFlares.push_back({ pos, color, timer_Time() }); return true; } Index: ps/trunk/source/ps/CStrInternStatic.h =================================================================== --- ps/trunk/source/ps/CStrInternStatic.h (revision 26900) +++ ps/trunk/source/ps/CStrInternStatic.h (revision 26901) @@ -1,192 +1,193 @@ /* 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 . */ // This file defines global CStrIntern variables, to avoid the cost of // constructing CStrInterns frequently at runtime. // // A line like // X(foo) // defines a variable str_foo with value "foo". // // A line like // X2(foo_0, "foo[0]") // defines a variable str_foo_0 with value "foo[0]". // For direct inclusion, we presumably just want the extern definitions. #ifndef X #include "CStrIntern.h" #define X(id) extern CStrIntern str_##id; #define X2(id, str) extern CStrIntern str_##id; #endif X(0) X(1) X(2) X(3) X(4) X(ALPHABLEND_PASS_BLEND) X(ALPHABLEND_PASS_OPAQUE) X(BLEND) X(BLOOM_NOP) X(BLOOM_PASS_H) X(BLOOM_PASS_V) X(DECAL) X(DISABLE_RECEIVE_SHADOWS) X(IGNORE_LOS) X(MINIMAP_BASE) X(MINIMAP_LINE) X(MINIMAP_LOS) X(MINIMAP_MASK) X(MINIMAP_POINT) X(MODE_SHADOWCAST) X(MODE_SILHOUETTEDISPLAY) X(MODE_SILHOUETTEOCCLUDER) X(MODE_WIREFRAME) X(MODE_WIREFRAME_SOLID) X(PASS_REFLECTIONS) X(PASS_REFRACTIONS) X(PASS_SHADOWS) X(RENDER_DEBUG_MODE) X(RENDER_DEBUG_MODE_AO) X(RENDER_DEBUG_MODE_ALPHA) X(RENDER_DEBUG_MODE_CUSTOM) X(RENDER_DEBUG_MODE_NONE) X(SHADOWS_CASCADE_COUNT) X(USE_FANCY_EFFECTS) X(USE_FP_SHADOW) +X(USE_GPU_INSTANCING) X(USE_GPU_SKINNING) X(USE_INSTANCING) X(USE_NORMALS) X(USE_OBJECTCOLOR) X(USE_REAL_DEPTH) X(USE_REFLECTION) X(USE_REFRACTION) X(USE_SHADOW) X(USE_SHADOW_PCF) X(USE_SHADOW_SAMPLER) X(USE_FOG) X(WATERTYPE_CLAP) X(WATERTYPE_LAKE) X2(_emptystring, "") X(a_apexPosition) X(a_otherPosition) X(a_retreatPosition) X(a_skinJoints) X(a_skinWeights) X(a_splashPosition) X(a_tangent) X(a_waterInfo) X(ambient) X(baseTex) X(blendTex) X(bloom) X(blurTex2) X(blurTex4) X(blurTex8) X(brightness) X(cameraForward) X(cameraPos) X(canvas2d) X(color) X(colorAdd) X(colorMul) X(debug_line) X(debug_overlay) X(delta) X(depthTex) X(dummy) X(foamTex) X(fogColor) X(fogParams) X(foreground_overlay) X(grayscaleFactor) X(hdr) X(height) X(instancingTransform) X(losTex) X(losTex1) X(losTex2) X(losTransform) X(los_interp) X(mapSize) X(maskTex) X(maskTextureTransform) X(minimap) X(modelViewMatrix) X(murkiness) X(normalMap) X(normalMap2) X(objectColor) X(overlay_line) X(overlay_solid) X(particle_add) X(particle_multiply) X(particle_overlay) X(particle_solid) X(particle_subtract) X(playerColor) X(projInvTransform) X(qualityLevel) X(reflectionMap) X(reflectionMatrix) X(refractionMap) X(refractionMatrix) X(renderedTex) X(repeatScale) X2(sans_10, "sans-10"); X(saturation) X(screenSize) X(shadingColor) X(shadowDistance) X(shadowDistances) X(shadowScale) X(shadowTex) X(shadowTransform) X(shadowTransforms) X(sharpness) X(skinBlendMatrices) X(skyBoxRot) X(skyCube) X(sky_simple) X(solid) X(sunColor) X(sunDir) X(terrain_solid) X(tex) X(texSize) X(textureTransform) X(time) X(tint) X(transform) X(translation) X(viewInvTransform) X(water_high) X(water_simple) X(water_waves) X(waterEffectsTex) X(waterTex) X(waveTex) X(waviness) X(waveParams1) X(waveParams2) X(width) X(windAngle) X(zFar) X(zNear) #undef X #undef X2 Index: ps/trunk/source/renderer/DebugRenderer.cpp =================================================================== --- ps/trunk/source/renderer/DebugRenderer.cpp (revision 26900) +++ ps/trunk/source/renderer/DebugRenderer.cpp (revision 26901) @@ -1,399 +1,411 @@ /* 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/DebugRenderer.h" #include "graphics/Camera.h" #include "graphics/Color.h" #include "graphics/ShaderManager.h" #include "graphics/ShaderProgram.h" #include "maths/BoundingBoxAligned.h" #include "maths/Brush.h" #include "maths/Matrix3D.h" #include "maths/Vector3D.h" #include "ps/CStrInternStatic.h" #include "renderer/backend/IDeviceCommandContext.h" #include "renderer/Renderer.h" #include "renderer/SceneRenderer.h" #include namespace { void SetGraphicsPipelineStateFromTechAndColor( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CShaderTechniquePtr& tech, const CColor& color, const bool depthTestEnabled = true, const bool wireframe = false) { Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = tech->GetGraphicsPipelineStateDesc(); pipelineStateDesc.depthStencilState.depthTestEnabled = depthTestEnabled; if (color.a != 1.0f) { pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; } else pipelineStateDesc.blendState.enabled = false; if (wireframe) pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE; pipelineStateDesc.rasterizationState.cullMode = Renderer::Backend::CullMode::NONE; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); } } // anonymous namespace void CDebugRenderer::DrawLine( const CVector3D& from, const CVector3D& to, const CColor& color, const float width, const bool depthTestEnabled) { if (from == to) return; DrawLine({from, to}, color, width, depthTestEnabled); } void CDebugRenderer::DrawLine( const std::vector& line, const CColor& color, const float width, const bool depthTestEnabled) { CShaderTechniquePtr debugLineTech = g_Renderer.GetShaderManager().LoadEffect(str_debug_line); Renderer::Backend::IDeviceCommandContext* deviceCommandContext = g_Renderer.GetDeviceCommandContext(); SetGraphicsPipelineStateFromTechAndColor( deviceCommandContext, debugLineTech, color, depthTestEnabled); deviceCommandContext->BeginPass(); const CCamera& viewCamera = g_Renderer.GetSceneRenderer().GetViewCamera(); Renderer::Backend::IShaderProgram* debugLineShader = debugLineTech->GetShader(); const CMatrix3D transform = viewCamera.GetViewProjection(); deviceCommandContext->SetUniform( debugLineShader->GetBindingSlot(str_transform), transform.AsFloatArray()); deviceCommandContext->SetUniform( debugLineShader->GetBindingSlot(str_color), color.AsFloatArray()); const CVector3D cameraIn = viewCamera.GetOrientation().GetIn(); std::vector vertices; vertices.reserve(line.size() * 6 * 3); #define ADD(position) \ vertices.emplace_back((position).X); \ vertices.emplace_back((position).Y); \ vertices.emplace_back((position).Z); for (size_t idx = 1; idx < line.size(); ++idx) { const CVector3D from = line[idx - 1]; const CVector3D to = line[idx]; const CVector3D direction = (to - from).Normalized(); const CVector3D view = direction.Dot(cameraIn) > 0.9f ? CVector3D(0.0f, 1.0f, 0.0f) : cameraIn; const CVector3D offset = view.Cross(direction).Normalized() * width; ADD(from + offset) ADD(to - offset) ADD(to + offset) ADD(from + offset) ADD(from - offset) ADD(to - offset) } #undef ADD deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, 0); - deviceCommandContext->SetVertexBufferData(0, vertices.data()); + Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); + deviceCommandContext->SetVertexBufferData( + 0, vertices.data(), vertices.size() * sizeof(vertices[0])); deviceCommandContext->Draw(0, vertices.size() / 3); deviceCommandContext->EndPass(); } void CDebugRenderer::DrawCircle(const CVector3D& origin, const float radius, const CColor& color) { CShaderTechniquePtr debugCircleTech = g_Renderer.GetShaderManager().LoadEffect(str_debug_line); Renderer::Backend::IDeviceCommandContext* deviceCommandContext = g_Renderer.GetDeviceCommandContext(); SetGraphicsPipelineStateFromTechAndColor( deviceCommandContext, debugCircleTech, color); deviceCommandContext->BeginPass(); const CCamera& camera = g_Renderer.GetSceneRenderer().GetViewCamera(); Renderer::Backend::IShaderProgram* debugCircleShader = debugCircleTech->GetShader(); const CMatrix3D transform = camera.GetViewProjection(); deviceCommandContext->SetUniform( debugCircleShader->GetBindingSlot(str_transform), transform.AsFloatArray()); deviceCommandContext->SetUniform( debugCircleShader->GetBindingSlot(str_color), color.AsFloatArray()); const CVector3D cameraUp = camera.GetOrientation().GetUp(); const CVector3D cameraLeft = camera.GetOrientation().GetLeft(); std::vector vertices; #define ADD(position) \ vertices.emplace_back((position).X); \ vertices.emplace_back((position).Y); \ vertices.emplace_back((position).Z); constexpr size_t segments = 16; for (size_t idx = 0; idx <= segments; ++idx) { const float angle = M_PI * 2.0f * idx / segments; const CVector3D offset = cameraUp * sin(angle) - cameraLeft * cos(angle); const float nextAngle = M_PI * 2.0f * (idx + 1) / segments; const CVector3D nextOffset = cameraUp * sin(nextAngle) - cameraLeft * cos(nextAngle); ADD(origin) ADD(origin + offset * radius) ADD(origin + nextOffset * radius) } #undef ADD deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, 0); - deviceCommandContext->SetVertexBufferData(0, vertices.data()); + Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); + deviceCommandContext->SetVertexBufferData( + 0, vertices.data(), vertices.size() * sizeof(vertices[0])); deviceCommandContext->Draw(0, vertices.size() / 3); deviceCommandContext->EndPass(); } void CDebugRenderer::DrawCameraFrustum(const CCamera& camera, const CColor& color, int intermediates, bool wireframe) { CCamera::Quad nearPoints; CCamera::Quad farPoints; camera.GetViewQuad(camera.GetNearPlane(), nearPoints); camera.GetViewQuad(camera.GetFarPlane(), farPoints); for (int i = 0; i < 4; ++i) { nearPoints[i] = camera.m_Orientation.Transform(nearPoints[i]); farPoints[i] = camera.m_Orientation.Transform(farPoints[i]); } CShaderTechniquePtr overlayTech = g_Renderer.GetShaderManager().LoadEffect(str_debug_line); Renderer::Backend::IDeviceCommandContext* deviceCommandContext = g_Renderer.GetDeviceCommandContext(); SetGraphicsPipelineStateFromTechAndColor( deviceCommandContext, overlayTech, color, true, wireframe); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* overlayShader = overlayTech->GetShader(); const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( overlayShader->GetBindingSlot(str_transform), transform.AsFloatArray()); deviceCommandContext->SetUniform( overlayShader->GetBindingSlot(str_color), color.AsFloatArray()); std::vector vertices; #define ADD(position) \ vertices.emplace_back((position).X); \ vertices.emplace_back((position).Y); \ vertices.emplace_back((position).Z); // Near plane. ADD(nearPoints[0]); ADD(nearPoints[1]); ADD(nearPoints[2]); ADD(nearPoints[0]); ADD(nearPoints[2]); ADD(nearPoints[3]); // Far plane. ADD(farPoints[0]); ADD(farPoints[1]); ADD(farPoints[2]); ADD(farPoints[0]); ADD(farPoints[2]); ADD(farPoints[3]); // Intermediate planes. CVector3D intermediatePoints[4]; for (int i = 0; i < intermediates; ++i) { const float t = (i + 1.0f) / (intermediates + 1.0f); for (int j = 0; j < 4; ++j) intermediatePoints[j] = nearPoints[j] * t + farPoints[j] * (1.0f - t); ADD(intermediatePoints[0]); ADD(intermediatePoints[1]); ADD(intermediatePoints[2]); ADD(intermediatePoints[0]); ADD(intermediatePoints[2]); ADD(intermediatePoints[3]); } deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, 0); - deviceCommandContext->SetVertexBufferData(0, vertices.data()); + Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); + deviceCommandContext->SetVertexBufferData( + 0, vertices.data(), vertices.size() * sizeof(vertices[0])); deviceCommandContext->Draw(0, vertices.size() / 3); vertices.clear(); // Connection lines. for (int i = 0; i < 4; ++i) { const int nextI = (i + 1) % 4; ADD(nearPoints[i]); ADD(farPoints[nextI]); ADD(farPoints[i]); ADD(nearPoints[i]); ADD(nearPoints[nextI]); ADD(farPoints[nextI]); } deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, 0); - deviceCommandContext->SetVertexBufferData(0, vertices.data()); + Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); + deviceCommandContext->SetVertexBufferData( + 0, vertices.data(), vertices.size() * sizeof(vertices[0])); deviceCommandContext->Draw(0, vertices.size() / 3); #undef ADD deviceCommandContext->EndPass(); } void CDebugRenderer::DrawBoundingBox( const CBoundingBoxAligned& boundingBox, const CColor& color, bool wireframe) { DrawBoundingBox( boundingBox, color, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection(), wireframe); } void CDebugRenderer::DrawBoundingBox( const CBoundingBoxAligned& boundingBox, const CColor& color, const CMatrix3D& transform, bool wireframe) { CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_solid); Renderer::Backend::IDeviceCommandContext* deviceCommandContext = g_Renderer.GetDeviceCommandContext(); SetGraphicsPipelineStateFromTechAndColor( deviceCommandContext, shaderTech, color, true, wireframe); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = shaderTech->GetShader(); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), transform.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_color), color.AsFloatArray()); std::vector data; #define ADD_FACE(x, y, z) \ ADD_PT(0, 0, x, y, z); ADD_PT(1, 0, x, y, z); ADD_PT(1, 1, x, y, z); \ ADD_PT(1, 1, x, y, z); ADD_PT(0, 1, x, y, z); ADD_PT(0, 0, x, y, z); #define ADD_PT(u_, v_, x, y, z) \ STMT(int u = u_; int v = v_; \ data.push_back(boundingBox[x].X); \ data.push_back(boundingBox[y].Y); \ data.push_back(boundingBox[z].Z); \ ) ADD_FACE(u, v, 0); ADD_FACE(0, u, v); ADD_FACE(u, 0, 1-v); ADD_FACE(u, 1-v, 1); ADD_FACE(1, u, 1-v); ADD_FACE(u, 1, v); #undef ADD_FACE deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, 0); - deviceCommandContext->SetVertexBufferData(0, data.data()); + Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); + deviceCommandContext->SetVertexBufferData( + 0, data.data(), data.size() * sizeof(data[0])); deviceCommandContext->Draw(0, 6 * 6); deviceCommandContext->EndPass(); } void CDebugRenderer::DrawBrush(const CBrush& brush, const CColor& color, bool wireframe) { CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_solid); Renderer::Backend::IDeviceCommandContext* deviceCommandContext = g_Renderer.GetDeviceCommandContext(); SetGraphicsPipelineStateFromTechAndColor( deviceCommandContext, shaderTech, color, true, wireframe); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = shaderTech->GetShader(); const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), transform.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_color), color.AsFloatArray()); std::vector data; std::vector> faces; brush.GetFaces(faces); #define ADD_VERT(a) \ STMT( \ data.push_back(brush.GetVertices()[faces[i][a]].X); \ data.push_back(brush.GetVertices()[faces[i][a]].Y); \ data.push_back(brush.GetVertices()[faces[i][a]].Z); \ ) for (size_t i = 0; i < faces.size(); ++i) { // Triangulate into (0,1,2), (0,2,3), ... for (size_t j = 1; j < faces[i].size() - 2; ++j) { ADD_VERT(0); ADD_VERT(j); ADD_VERT(j+1); } } #undef ADD_VERT deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, 0); - deviceCommandContext->SetVertexBufferData(0, data.data()); + Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); + deviceCommandContext->SetVertexBufferData( + 0, data.data(), data.size() * sizeof(data[0])); deviceCommandContext->Draw(0, data.size() / 5); deviceCommandContext->EndPass(); } Index: ps/trunk/source/renderer/DecalRData.cpp =================================================================== --- ps/trunk/source/renderer/DecalRData.cpp (revision 26900) +++ ps/trunk/source/renderer/DecalRData.cpp (revision 26901) @@ -1,372 +1,375 @@ /* 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 "DecalRData.h" #include "graphics/Decal.h" #include "graphics/Model.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "graphics/TextureManager.h" #include "lib/allocators/DynamicArena.h" #include "lib/allocators/STLAllocators.h" #include "ps/CLogger.h" #include "ps/CStrInternStatic.h" #include "ps/Game.h" #include "ps/Profile.h" #include "renderer/Renderer.h" #include "renderer/TerrainRenderer.h" #include "simulation2/components/ICmpWaterManager.h" #include "simulation2/Simulation2.h" #include // TODO: Currently each decal is a separate CDecalRData. We might want to use // lots of decals for special effects like shadows, footprints, etc, in which // case we should probably redesign this to batch them all together for more // efficient rendering. namespace { struct SDecalBatch { CDecalRData* decal; CStrIntern shaderEffect; CShaderDefines shaderDefines; CVertexBuffer::VBChunk* vertices; CVertexBuffer::VBChunk* indices; }; struct SDecalBatchComparator { bool operator()(const SDecalBatch& lhs, const SDecalBatch& rhs) const { if (lhs.shaderEffect != rhs.shaderEffect) return lhs.shaderEffect < rhs.shaderEffect; if (lhs.shaderDefines != rhs.shaderDefines) return lhs.shaderDefines < rhs.shaderDefines; const CMaterial& lhsMaterial = lhs.decal->GetDecal()->m_Decal.m_Material; const CMaterial& rhsMaterial = rhs.decal->GetDecal()->m_Decal.m_Material; if (lhsMaterial.GetDiffuseTexture() != rhsMaterial.GetDiffuseTexture()) return lhsMaterial.GetDiffuseTexture() < rhsMaterial.GetDiffuseTexture(); if (lhs.vertices->m_Owner != rhs.vertices->m_Owner) return lhs.vertices->m_Owner < rhs.vertices->m_Owner; if (lhs.indices->m_Owner != rhs.indices->m_Owner) return lhs.indices->m_Owner < rhs.indices->m_Owner; return lhs.decal < rhs.decal; } }; } // anonymous namespace CDecalRData::CDecalRData(CModelDecal* decal, CSimulation2* simulation) : m_Decal(decal), m_Simulation(simulation) { BuildVertexData(); } CDecalRData::~CDecalRData() = default; void CDecalRData::Update(CSimulation2* simulation) { m_Simulation = simulation; if (m_UpdateFlags != 0) { BuildVertexData(); m_UpdateFlags = 0; } } void CDecalRData::RenderDecals( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const std::vector& decals, const CShaderDefines& context, ShadowMap* shadow) { PROFILE3("render terrain decals"); GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain decals"); using Arena = Allocators::DynamicArena<256 * KiB>; Arena arena; using Batches = std::vector>; Batches batches((Batches::allocator_type(arena))); batches.reserve(decals.size()); CShaderDefines contextDecal = context; contextDecal.Add(str_DECAL, str_1); for (CDecalRData* decal : decals) { CMaterial& material = decal->m_Decal->m_Decal.m_Material; if (material.GetShaderEffect().empty()) { LOGERROR("Terrain renderer failed to load shader effect.\n"); continue; } if (material.GetSamplers().empty() || !decal->m_VBDecals || !decal->m_VBDecalsIndices) continue; SDecalBatch batch; batch.decal = decal; batch.shaderEffect = material.GetShaderEffect(); batch.shaderDefines = material.GetShaderDefines(); batch.vertices = decal->m_VBDecals.Get(); batch.indices = decal->m_VBDecalsIndices.Get(); batches.emplace_back(std::move(batch)); } if (batches.empty()) return; std::sort(batches.begin(), batches.end(), SDecalBatchComparator()); CVertexBuffer* lastIB = nullptr; for (auto itTechBegin = batches.begin(), itTechEnd = batches.begin(); itTechBegin != batches.end(); itTechBegin = itTechEnd) { while (itTechEnd != batches.end() && itTechBegin->shaderEffect == itTechEnd->shaderEffect && itTechBegin->shaderDefines == itTechEnd->shaderDefines) { ++itTechEnd; } CShaderDefines defines = contextDecal; defines.SetMany(itTechBegin->shaderDefines); CShaderTechniquePtr techBase = g_Renderer.GetShaderManager().LoadEffect( itTechBegin->shaderEffect, defines); if (!techBase) { LOGERROR("Terrain renderer failed to load shader effect (%s)\n", itTechBegin->shaderEffect.c_str()); continue; } const int numPasses = techBase->GetNumPasses(); for (int pass = 0; pass < numPasses; ++pass) { Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = techBase->GetGraphicsPipelineStateDesc(pass); pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; pipelineStateDesc.depthStencilState.depthWriteEnabled = false; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = techBase->GetShader(pass); TerrainRenderer::PrepareShader(deviceCommandContext, shader, shadow); CColor shadingColor(1.0f, 1.0f, 1.0f, 1.0f); const int32_t shadingColorBindingSlot = shader->GetBindingSlot(str_shadingColor); deviceCommandContext->SetUniform( shadingColorBindingSlot, shadingColor.AsFloatArray()); CShaderUniforms currentStaticUniforms; CVertexBuffer* lastVB = nullptr; for (auto itDecal = itTechBegin; itDecal != itTechEnd; ++itDecal) { SDecalBatch& batch = *itDecal; CDecalRData* decal = batch.decal; CMaterial& material = decal->m_Decal->m_Decal.m_Material; const CMaterial::SamplersVector& samplers = material.GetSamplers(); for (const CMaterial::TextureSampler& sampler : samplers) sampler.Sampler->UploadBackendTextureIfNeeded(deviceCommandContext); for (const CMaterial::TextureSampler& sampler : samplers) { deviceCommandContext->SetTexture( shader->GetBindingSlot(sampler.Name), sampler.Sampler->GetBackendTexture()); } if (currentStaticUniforms != material.GetStaticUniforms()) { currentStaticUniforms = material.GetStaticUniforms(); material.GetStaticUniforms().BindUniforms(deviceCommandContext, shader); } // TODO: Need to handle floating decals correctly. In particular, we need // to render non-floating before water and floating after water (to get // the blending right), and we also need to apply the correct lighting in // each case, which doesn't really seem possible with the current // TerrainRenderer. // Also, need to mark the decals as dirty when water height changes. // m_Decal->GetBounds().Render(); if (shadingColor != decal->m_Decal->GetShadingColor()) { shadingColor = decal->m_Decal->GetShadingColor(); deviceCommandContext->SetUniform( shadingColorBindingSlot, shadingColor.AsFloatArray()); } if (lastVB != batch.vertices->m_Owner) { lastVB = batch.vertices->m_Owner; batch.vertices->m_Owner->UploadIfNeeded(deviceCommandContext); const uint32_t stride = sizeof(SDecalVertex); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, Renderer::Backend::Format::R32G32B32_SFLOAT, - offsetof(SDecalVertex, m_Position), stride, 0); + offsetof(SDecalVertex, m_Position), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::NORMAL, Renderer::Backend::Format::R32G32B32_SFLOAT, - offsetof(SDecalVertex, m_Normal), stride, 0); + offsetof(SDecalVertex, m_Normal), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, Renderer::Backend::Format::R32G32_SFLOAT, - offsetof(SDecalVertex, m_UV), stride, 0); + offsetof(SDecalVertex, m_UV), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexBuffer(0, batch.vertices->m_Owner->GetBuffer()); } if (lastIB != batch.indices->m_Owner) { lastIB = batch.indices->m_Owner; batch.indices->m_Owner->UploadIfNeeded(deviceCommandContext); deviceCommandContext->SetIndexBuffer(batch.indices->m_Owner->GetBuffer()); } deviceCommandContext->DrawIndexed(batch.indices->m_Index, batch.indices->m_Count, 0); // bump stats g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_TerrainTris += batch.indices->m_Count / 3; } deviceCommandContext->EndPass(); } } } void CDecalRData::BuildVertexData() { PROFILE("decal build"); const SDecal& decal = m_Decal->m_Decal; // TODO: Currently this constructs an axis-aligned bounding rectangle around // the decal. It would be more efficient for rendering if we excluded tiles // that are outside the (non-axis-aligned) decal rectangle. ssize_t i0, j0, i1, j1; m_Decal->CalcVertexExtents(i0, j0, i1, j1); // Currently CalcVertexExtents might return empty rectangle, that means // we can't render it. if (i1 <= i0 || j1 <= j0) { // We have nothing to render. m_VBDecals.Reset(); m_VBDecalsIndices.Reset(); return; } CmpPtr cmpWaterManager(*m_Simulation, SYSTEM_ENTITY); std::vector vertices((i1 - i0 + 1) * (j1 - j0 + 1)); for (ssize_t j = j0, idx = 0; j <= j1; ++j) { for (ssize_t i = i0; i <= i1; ++i, ++idx) { SDecalVertex& vertex = vertices[idx]; m_Decal->m_Terrain->CalcPosition(i, j, vertex.m_Position); if (decal.m_Floating && cmpWaterManager) { vertex.m_Position.Y = std::max( vertex.m_Position.Y, cmpWaterManager->GetExactWaterLevel(vertex.m_Position.X, vertex.m_Position.Z)); } m_Decal->m_Terrain->CalcNormal(i, j, vertex.m_Normal); // Map from world space back into decal texture space. CVector3D inv = m_Decal->GetInvTransform().Transform(vertex.m_Position); vertex.m_UV.X = 0.5f + (inv.X - decal.m_OffsetX) / decal.m_SizeX; // Flip V to match our texture convention. vertex.m_UV.Y = 0.5f - (inv.Z - decal.m_OffsetZ) / decal.m_SizeZ; } } if (!m_VBDecals || m_VBDecals->m_Count != vertices.size()) { m_VBDecals = g_VBMan.AllocateChunk( sizeof(SDecalVertex), vertices.size(), Renderer::Backend::IBuffer::Type::VERTEX, false); } m_VBDecals->m_Owner->UpdateChunkVertices(m_VBDecals.Get(), vertices.data()); std::vector indices((i1 - i0) * (j1 - j0) * 6); const ssize_t w = i1 - i0 + 1; auto itIdx = indices.begin(); const size_t base = m_VBDecals->m_Index; for (ssize_t dj = 0; dj < j1 - j0; ++dj) { for (ssize_t di = 0; di < i1 - i0; ++di) { const bool dir = m_Decal->m_Terrain->GetTriangulationDir(i0 + di, j0 + dj); if (dir) { *itIdx++ = u16(((dj + 0) * w + (di + 0)) + base); *itIdx++ = u16(((dj + 0) * w + (di + 1)) + base); *itIdx++ = u16(((dj + 1) * w + (di + 0)) + base); *itIdx++ = u16(((dj + 0) * w + (di + 1)) + base); *itIdx++ = u16(((dj + 1) * w + (di + 1)) + base); *itIdx++ = u16(((dj + 1) * w + (di + 0)) + base); } else { *itIdx++ = u16(((dj + 0) * w + (di + 0)) + base); *itIdx++ = u16(((dj + 0) * w + (di + 1)) + base); *itIdx++ = u16(((dj + 1) * w + (di + 1)) + base); *itIdx++ = u16(((dj + 1) * w + (di + 1)) + base); *itIdx++ = u16(((dj + 1) * w + (di + 0)) + base); *itIdx++ = u16(((dj + 0) * w + (di + 0)) + base); } } } // Construct vertex buffer. if (!m_VBDecalsIndices || m_VBDecalsIndices->m_Count != indices.size()) { m_VBDecalsIndices = g_VBMan.AllocateChunk( sizeof(u16), indices.size(), Renderer::Backend::IBuffer::Type::INDEX, false); } m_VBDecalsIndices->m_Owner->UpdateChunkVertices(m_VBDecalsIndices.Get(), indices.data()); } Index: ps/trunk/source/renderer/HWLightingModelRenderer.cpp =================================================================== --- ps/trunk/source/renderer/HWLightingModelRenderer.cpp (revision 26900) +++ ps/trunk/source/renderer/HWLightingModelRenderer.cpp (revision 26901) @@ -1,251 +1,255 @@ /* 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/HWLightingModelRenderer.h" #include "graphics/Color.h" #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "graphics/ShaderProgram.h" #include "lib/bits.h" #include "lib/sysdep/rtl.h" #include "maths/Vector3D.h" #include "renderer/Renderer.h" #include "renderer/RenderModifiers.h" #include "renderer/VertexArray.h" struct ShaderModelDef : public CModelDefRPrivate { /// Indices are the same for all models, so share them VertexIndexArray m_IndexArray; /// Static per-CModelDef vertex array VertexArray m_Array; /// The number of UVs is determined by the model std::vector m_UVs; ShaderModelDef(const CModelDefPtr& mdef); }; ShaderModelDef::ShaderModelDef(const CModelDefPtr& mdef) : m_IndexArray(false), m_Array(Renderer::Backend::IBuffer::Type::VERTEX, false) { size_t numVertices = mdef->GetNumVertices(); m_UVs.resize(mdef->GetNumUVsPerVertex()); for (size_t i = 0; i < mdef->GetNumUVsPerVertex(); ++i) { m_UVs[i].format = Renderer::Backend::Format::R32G32_SFLOAT; m_Array.AddAttribute(&m_UVs[i]); } m_Array.SetNumberOfVertices(numVertices); m_Array.Layout(); for (size_t i = 0; i < mdef->GetNumUVsPerVertex(); ++i) { VertexArrayIterator UVit = m_UVs[i].GetIterator(); ModelRenderer::BuildUV(mdef, UVit, i); } m_Array.Upload(); m_Array.FreeBackingStore(); m_IndexArray.SetNumberOfVertices(mdef->GetNumFaces()*3); m_IndexArray.Layout(); ModelRenderer::BuildIndices(mdef, m_IndexArray.GetIterator()); m_IndexArray.Upload(); m_IndexArray.FreeBackingStore(); } struct ShaderModel : public CModelRData { /// Dynamic per-CModel vertex array VertexArray m_Array; /// Position and normals/lighting are recalculated on CPU every frame VertexArray::Attribute m_Position; VertexArray::Attribute m_Normal; ShaderModel(const void* key) : CModelRData(key), m_Array(Renderer::Backend::IBuffer::Type::VERTEX, true) {} }; struct ShaderModelVertexRenderer::ShaderModelRendererInternals { /// Previously prepared modeldef ShaderModelDef* shadermodeldef; }; // Construction and Destruction ShaderModelVertexRenderer::ShaderModelVertexRenderer() { m = new ShaderModelRendererInternals; m->shadermodeldef = nullptr; } ShaderModelVertexRenderer::~ShaderModelVertexRenderer() { delete m; } // Build model data (and modeldef data if necessary) CModelRData* ShaderModelVertexRenderer::CreateModelData(const void* key, CModel* model) { CModelDefPtr mdef = model->GetModelDef(); ShaderModelDef* shadermodeldef = (ShaderModelDef*)mdef->GetRenderData(m); if (!shadermodeldef) { shadermodeldef = new ShaderModelDef(mdef); mdef->SetRenderData(m, shadermodeldef); } // Build the per-model data ShaderModel* shadermodel = new ShaderModel(key); // Positions and normals must be 16-byte aligned for SSE writes. shadermodel->m_Position.format = Renderer::Backend::Format::R32G32B32A32_SFLOAT; shadermodel->m_Array.AddAttribute(&shadermodel->m_Position); shadermodel->m_Normal.format = Renderer::Backend::Format::R32G32B32A32_SFLOAT; shadermodel->m_Array.AddAttribute(&shadermodel->m_Normal); shadermodel->m_Array.SetNumberOfVertices(mdef->GetNumVertices()); shadermodel->m_Array.Layout(); // Verify alignment ENSURE(shadermodel->m_Position.offset % 16 == 0); ENSURE(shadermodel->m_Normal.offset % 16 == 0); ENSURE(shadermodel->m_Array.GetStride() % 16 == 0); return shadermodel; } // Fill in and upload dynamic vertex array void ShaderModelVertexRenderer::UpdateModelData(CModel* model, CModelRData* data, int updateflags) { ShaderModel* shadermodel = static_cast(data); if (updateflags & RENDERDATA_UPDATE_VERTICES) { // build vertices VertexArrayIterator Position = shadermodel->m_Position.GetIterator(); VertexArrayIterator Normal = shadermodel->m_Normal.GetIterator(); ModelRenderer::BuildPositionAndNormals(model, Position, Normal); // upload everything to vertex buffer shadermodel->m_Array.Upload(); } shadermodel->m_Array.PrepareForRendering(); } // Setup one rendering pass void ShaderModelVertexRenderer::BeginPass() { } // Cleanup one rendering pass void ShaderModelVertexRenderer::EndPass( Renderer::Backend::IDeviceCommandContext* UNUSED(deviceCommandContext)) { } // Prepare UV coordinates for this modeldef void ShaderModelVertexRenderer::PrepareModelDef( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CModelDef& def) { m->shadermodeldef = (ShaderModelDef*)def.GetRenderData(m); ENSURE(m->shadermodeldef); m->shadermodeldef->m_Array.UploadIfNeeded(deviceCommandContext); const uint32_t stride = m->shadermodeldef->m_Array.GetStride(); const uint32_t firstVertexOffset = m->shadermodeldef->m_Array.GetOffset() * stride; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, m->shadermodeldef->m_UVs[0].format, - firstVertexOffset + m->shadermodeldef->m_UVs[0].offset, stride, 0); + firstVertexOffset + m->shadermodeldef->m_UVs[0].offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); if (def.GetNumUVsPerVertex() >= 2) { deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV1, m->shadermodeldef->m_UVs[1].format, - firstVertexOffset + m->shadermodeldef->m_UVs[1].offset, stride, 0); + firstVertexOffset + m->shadermodeldef->m_UVs[1].offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); } deviceCommandContext->SetVertexBuffer(0, m->shadermodeldef->m_Array.GetBuffer()); } // Render one model void ShaderModelVertexRenderer::RenderModel( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IShaderProgram* UNUSED(shader), CModel* model, CModelRData* data) { const CModelDefPtr& mdldef = model->GetModelDef(); ShaderModel* shadermodel = static_cast(data); shadermodel->m_Array.UploadIfNeeded(deviceCommandContext); m->shadermodeldef->m_IndexArray.UploadIfNeeded(deviceCommandContext); const uint32_t stride = shadermodel->m_Array.GetStride(); const uint32_t firstVertexOffset = shadermodel->m_Array.GetOffset() * stride; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, Renderer::Backend::Format::R32G32B32_SFLOAT, - firstVertexOffset + shadermodel->m_Position.offset, stride, 1); + firstVertexOffset + shadermodel->m_Position.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::NORMAL, Renderer::Backend::Format::R32G32B32_SFLOAT, - firstVertexOffset + shadermodel->m_Normal.offset, stride, 1); + firstVertexOffset + shadermodel->m_Normal.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1); deviceCommandContext->SetVertexBuffer(1, shadermodel->m_Array.GetBuffer()); deviceCommandContext->SetIndexBuffer(m->shadermodeldef->m_IndexArray.GetBuffer()); // Render the lot. const size_t numberOfFaces = mdldef->GetNumFaces(); deviceCommandContext->DrawIndexedInRange( m->shadermodeldef->m_IndexArray.GetOffset(), numberOfFaces * 3, 0, mdldef->GetNumVertices() - 1); // Bump stats. g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_ModelTris += numberOfFaces; } Index: ps/trunk/source/renderer/InstancingModelRenderer.cpp =================================================================== --- ps/trunk/source/renderer/InstancingModelRenderer.cpp (revision 26900) +++ ps/trunk/source/renderer/InstancingModelRenderer.cpp (revision 26901) @@ -1,395 +1,401 @@ /* 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/InstancingModelRenderer.h" #include "graphics/Color.h" #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" #include "ps/CLogger.h" #include "ps/CStrInternStatic.h" #include "renderer/Renderer.h" #include "renderer/RenderModifiers.h" #include "renderer/VertexArray.h" #include "third_party/mikktspace/weldmesh.h" struct IModelDef : public CModelDefRPrivate { /// Static per-CModel vertex array VertexArray m_Array; /// Position and normals are static VertexArray::Attribute m_Position; VertexArray::Attribute m_Normal; VertexArray::Attribute m_Tangent; VertexArray::Attribute m_BlendJoints; // valid iff gpuSkinning == true VertexArray::Attribute m_BlendWeights; // valid iff gpuSkinning == true /// The number of UVs is determined by the model std::vector m_UVs; /// Indices are the same for all models, so share them VertexIndexArray m_IndexArray; IModelDef(const CModelDefPtr& mdef, bool gpuSkinning, bool calculateTangents); }; IModelDef::IModelDef(const CModelDefPtr& mdef, bool gpuSkinning, bool calculateTangents) : m_IndexArray(false), m_Array(Renderer::Backend::IBuffer::Type::VERTEX, false) { size_t numVertices = mdef->GetNumVertices(); m_Position.format = Renderer::Backend::Format::R32G32B32_SFLOAT; m_Array.AddAttribute(&m_Position); m_Normal.format = Renderer::Backend::Format::R32G32B32_SFLOAT; m_Array.AddAttribute(&m_Normal); m_UVs.resize(mdef->GetNumUVsPerVertex()); for (size_t i = 0; i < mdef->GetNumUVsPerVertex(); i++) { m_UVs[i].format = Renderer::Backend::Format::R32G32_SFLOAT; m_Array.AddAttribute(&m_UVs[i]); } if (gpuSkinning) { // We can't use a lot of bones because it costs uniform memory. Recommended // number of bones per model is 32. // Add 1 to NumBones because of the special 'root' bone. if (mdef->GetNumBones() + 1 > 64) LOGERROR("Model '%s' has too many bones %zu/64", mdef->GetName().string8().c_str(), mdef->GetNumBones() + 1); ENSURE(mdef->GetNumBones() + 1 <= 64); m_BlendJoints.format = Renderer::Backend::Format::R8G8B8A8_UINT; m_Array.AddAttribute(&m_BlendJoints); m_BlendWeights.format = Renderer::Backend::Format::R8G8B8A8_UNORM; m_Array.AddAttribute(&m_BlendWeights); } if (calculateTangents) { // Generate tangents for the geometry:- m_Tangent.format = Renderer::Backend::Format::R32G32B32A32_SFLOAT; m_Array.AddAttribute(&m_Tangent); // floats per vertex; position + normal + tangent + UV*sets [+ GPUskinning] int numVertexAttrs = 3 + 3 + 4 + 2 * mdef->GetNumUVsPerVertex(); if (gpuSkinning) { numVertexAttrs += 8; } // the tangent generation can increase the number of vertices temporarily // so reserve a bit more memory to avoid reallocations in GenTangents (in most cases) std::vector newVertices; newVertices.reserve(numVertexAttrs * numVertices * 2); // Generate the tangents ModelRenderer::GenTangents(mdef, newVertices, gpuSkinning); // how many vertices do we have after generating tangents? int newNumVert = newVertices.size() / numVertexAttrs; std::vector remapTable(newNumVert); std::vector vertexDataOut(newNumVert * numVertexAttrs); // re-weld the mesh to remove duplicated vertices int numVertices2 = WeldMesh(&remapTable[0], &vertexDataOut[0], &newVertices[0], newNumVert, numVertexAttrs); // Copy the model data to graphics memory:- m_Array.SetNumberOfVertices(numVertices2); m_Array.Layout(); VertexArrayIterator Position = m_Position.GetIterator(); VertexArrayIterator Normal = m_Normal.GetIterator(); VertexArrayIterator Tangent = m_Tangent.GetIterator(); VertexArrayIterator BlendJoints; VertexArrayIterator BlendWeights; if (gpuSkinning) { BlendJoints = m_BlendJoints.GetIterator(); BlendWeights = m_BlendWeights.GetIterator(); } // copy everything into the vertex array for (int i = 0; i < numVertices2; i++) { int q = numVertexAttrs * i; Position[i] = CVector3D(vertexDataOut[q + 0], vertexDataOut[q + 1], vertexDataOut[q + 2]); q += 3; Normal[i] = CVector3D(vertexDataOut[q + 0], vertexDataOut[q + 1], vertexDataOut[q + 2]); q += 3; Tangent[i] = CVector4D(vertexDataOut[q + 0], vertexDataOut[q + 1], vertexDataOut[q + 2], vertexDataOut[q + 3]); q += 4; if (gpuSkinning) { for (size_t j = 0; j < 4; ++j) { BlendJoints[i][j] = (u8)vertexDataOut[q + 0 + 2 * j]; BlendWeights[i][j] = (u8)vertexDataOut[q + 1 + 2 * j]; } q += 8; } for (size_t j = 0; j < mdef->GetNumUVsPerVertex(); j++) { VertexArrayIterator UVit = m_UVs[j].GetIterator(); UVit[i][0] = vertexDataOut[q + 0 + 2 * j]; UVit[i][1] = vertexDataOut[q + 1 + 2 * j]; } } // upload vertex data m_Array.Upload(); m_Array.FreeBackingStore(); m_IndexArray.SetNumberOfVertices(mdef->GetNumFaces() * 3); m_IndexArray.Layout(); VertexArrayIterator Indices = m_IndexArray.GetIterator(); size_t idxidx = 0; // reindex geometry and upload index for (size_t j = 0; j < mdef->GetNumFaces(); ++j) { Indices[idxidx++] = remapTable[j * 3 + 0]; Indices[idxidx++] = remapTable[j * 3 + 1]; Indices[idxidx++] = remapTable[j * 3 + 2]; } m_IndexArray.Upload(); m_IndexArray.FreeBackingStore(); } else { // Upload model without calculating tangents:- m_Array.SetNumberOfVertices(numVertices); m_Array.Layout(); VertexArrayIterator Position = m_Position.GetIterator(); VertexArrayIterator Normal = m_Normal.GetIterator(); ModelRenderer::CopyPositionAndNormals(mdef, Position, Normal); for (size_t i = 0; i < mdef->GetNumUVsPerVertex(); i++) { VertexArrayIterator UVit = m_UVs[i].GetIterator(); ModelRenderer::BuildUV(mdef, UVit, i); } if (gpuSkinning) { VertexArrayIterator BlendJoints = m_BlendJoints.GetIterator(); VertexArrayIterator BlendWeights = m_BlendWeights.GetIterator(); for (size_t i = 0; i < numVertices; ++i) { const SModelVertex& vtx = mdef->GetVertices()[i]; for (size_t j = 0; j < 4; ++j) { BlendJoints[i][j] = vtx.m_Blend.m_Bone[j]; BlendWeights[i][j] = (u8)(255.f * vtx.m_Blend.m_Weight[j]); } } } m_Array.Upload(); m_Array.FreeBackingStore(); m_IndexArray.SetNumberOfVertices(mdef->GetNumFaces()*3); m_IndexArray.Layout(); ModelRenderer::BuildIndices(mdef, m_IndexArray.GetIterator()); m_IndexArray.Upload(); m_IndexArray.FreeBackingStore(); } } struct InstancingModelRendererInternals { bool gpuSkinning; bool calculateTangents; /// Previously prepared modeldef IModelDef* imodeldef; /// Index base for imodeldef u8* imodeldefIndexBase; }; // Construction and Destruction InstancingModelRenderer::InstancingModelRenderer(bool gpuSkinning, bool calculateTangents) { m = new InstancingModelRendererInternals; m->gpuSkinning = gpuSkinning; m->calculateTangents = calculateTangents; m->imodeldef = 0; } InstancingModelRenderer::~InstancingModelRenderer() { delete m; } // Build modeldef data if necessary - we have no per-CModel data CModelRData* InstancingModelRenderer::CreateModelData(const void* key, CModel* model) { CModelDefPtr mdef = model->GetModelDef(); IModelDef* imodeldef = (IModelDef*)mdef->GetRenderData(m); if (m->gpuSkinning) ENSURE(model->IsSkinned()); else ENSURE(!model->IsSkinned()); if (!imodeldef) { imodeldef = new IModelDef(mdef, m->gpuSkinning, m->calculateTangents); mdef->SetRenderData(m, imodeldef); } return new CModelRData(key); } void InstancingModelRenderer::UpdateModelData(CModel* UNUSED(model), CModelRData* UNUSED(data), int UNUSED(updateflags)) { // We have no per-CModel data } // Setup one rendering pass. void InstancingModelRenderer::BeginPass() { } // Cleanup rendering pass. void InstancingModelRenderer::EndPass( Renderer::Backend::IDeviceCommandContext* UNUSED(deviceCommandContext)) { } // Prepare UV coordinates for this modeldef void InstancingModelRenderer::PrepareModelDef( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CModelDef& def) { m->imodeldef = (IModelDef*)def.GetRenderData(m); ENSURE(m->imodeldef); m->imodeldef->m_Array.UploadIfNeeded(deviceCommandContext); m->imodeldef->m_IndexArray.UploadIfNeeded(deviceCommandContext); deviceCommandContext->SetIndexBuffer(m->imodeldef->m_IndexArray.GetBuffer()); const uint32_t stride = m->imodeldef->m_Array.GetStride(); const uint32_t firstVertexOffset = m->imodeldef->m_Array.GetOffset() * stride; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, m->imodeldef->m_Position.format, - firstVertexOffset + m->imodeldef->m_Position.offset, stride, 0); + firstVertexOffset + m->imodeldef->m_Position.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::NORMAL, m->imodeldef->m_Normal.format, - firstVertexOffset + m->imodeldef->m_Normal.offset, stride, 0); + firstVertexOffset + m->imodeldef->m_Normal.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); constexpr size_t MAX_UV = 2; for (size_t uv = 0; uv < std::min(MAX_UV, def.GetNumUVsPerVertex()); ++uv) { const Renderer::Backend::VertexAttributeStream stream = static_cast( static_cast(Renderer::Backend::VertexAttributeStream::UV0) + uv); deviceCommandContext->SetVertexAttributeFormat( stream, m->imodeldef->m_UVs[uv].format, - firstVertexOffset + m->imodeldef->m_UVs[uv].offset, stride, 0); + firstVertexOffset + m->imodeldef->m_UVs[uv].offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); } // GPU skinning requires extra attributes to compute positions/normals. if (m->gpuSkinning) { deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV2, m->imodeldef->m_BlendJoints.format, - firstVertexOffset + m->imodeldef->m_BlendJoints.offset, stride, 0); + firstVertexOffset + m->imodeldef->m_BlendJoints.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV3, m->imodeldef->m_BlendWeights.format, - firstVertexOffset + m->imodeldef->m_BlendWeights.offset, stride, 0); + firstVertexOffset + m->imodeldef->m_BlendWeights.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); } if (m->calculateTangents) { deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV4, m->imodeldef->m_Tangent.format, - firstVertexOffset + m->imodeldef->m_Tangent.offset, stride, 0); + firstVertexOffset + m->imodeldef->m_Tangent.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); } deviceCommandContext->SetVertexBuffer(0, m->imodeldef->m_Array.GetBuffer()); } // Render one model void InstancingModelRenderer::RenderModel( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IShaderProgram* shader, CModel* model, CModelRData* UNUSED(data)) { const CModelDefPtr& mdldef = model->GetModelDef(); if (m->gpuSkinning) { // Bind matrices for current animation state. // Add 1 to NumBones because of the special 'root' bone. deviceCommandContext->SetUniform( shader->GetBindingSlot(str_skinBlendMatrices), PS::span( model->GetAnimatedBoneMatrices()[0]._data, model->GetAnimatedBoneMatrices()[0].AsFloatArray().size() * (mdldef->GetNumBones() + 1))); } // Render the lot. const size_t numberOfFaces = mdldef->GetNumFaces(); deviceCommandContext->DrawIndexedInRange( m->imodeldef->m_IndexArray.GetOffset(), numberOfFaces * 3, 0, m->imodeldef->m_Array.GetNumberOfVertices() - 1); // Bump stats. g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_ModelTris += numberOfFaces; } Index: ps/trunk/source/renderer/OverlayRenderer.cpp =================================================================== --- ps/trunk/source/renderer/OverlayRenderer.cpp (revision 26900) +++ ps/trunk/source/renderer/OverlayRenderer.cpp (revision 26901) @@ -1,806 +1,817 @@ /* 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 "OverlayRenderer.h" #include "graphics/Camera.h" #include "graphics/LOSTexture.h" #include "graphics/Overlay.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "graphics/TextureManager.h" #include "lib/hash.h" #include "maths/MathUtil.h" #include "maths/Quaternion.h" #include "ps/CStrInternStatic.h" #include "ps/Game.h" #include "ps/Profile.h" #include "renderer/DebugRenderer.h" #include "renderer/Renderer.h" #include "renderer/SceneRenderer.h" #include "renderer/TexturedLineRData.h" #include "renderer/VertexArray.h" #include "renderer/VertexBuffer.h" #include "renderer/VertexBufferManager.h" #include "simulation2/components/ICmpWaterManager.h" #include "simulation2/Simulation2.h" #include "simulation2/system/SimContext.h" #include namespace { CShaderTechniquePtr GetOverlayLineShaderTechnique(const CShaderDefines& defines) { return g_Renderer.GetShaderManager().LoadEffect(str_overlay_line, defines); } } // anonymous namespace /** * Key used to group quads into batches for more efficient rendering. Currently groups by the combination * of the main texture and the texture mask, to minimize texture swapping during rendering. */ struct QuadBatchKey { QuadBatchKey (const CTexturePtr& texture, const CTexturePtr& textureMask) : m_Texture(texture), m_TextureMask(textureMask) { } bool operator==(const QuadBatchKey& other) const { return (m_Texture == other.m_Texture && m_TextureMask == other.m_TextureMask); } CTexturePtr m_Texture; CTexturePtr m_TextureMask; }; struct QuadBatchHash { std::size_t operator()(const QuadBatchKey& d) const { size_t seed = 0; hash_combine(seed, d.m_Texture); hash_combine(seed, d.m_TextureMask); return seed; } }; /** * Holds information about a single quad rendering batch. */ class QuadBatchData : public CRenderData { public: QuadBatchData() : m_IndicesBase(0), m_NumRenderQuads(0) { } /// Holds the quad overlay structures requested to be rendered in this batch. Must be cleared /// after each frame. std::vector m_Quads; /// Start index of this batch into the dedicated quad indices VertexArray (see OverlayInternals). size_t m_IndicesBase; /// Amount of quads to actually render in this batch. Potentially (although unlikely to be) /// different from m_Quads.size() due to restrictions on the total amount of quads that can be /// rendered. Must be reset after each frame. size_t m_NumRenderQuads; }; struct OverlayRendererInternals { using QuadBatchMap = std::unordered_map; OverlayRendererInternals(); ~OverlayRendererInternals(){ } std::vector lines; std::vector texlines; std::vector sprites; std::vector quads; std::vector spheres; QuadBatchMap quadBatchMap; // Dedicated vertex/index buffers for rendering all quads (to within the limits set by // MAX_QUAD_OVERLAYS). VertexArray quadVertices; VertexArray::Attribute quadAttributePos; VertexArray::Attribute quadAttributeColor; VertexArray::Attribute quadAttributeUV; VertexIndexArray quadIndices; // Maximum amount of quad overlays we support for rendering. This limit is set to be able to // render all quads from a single dedicated VB without having to reallocate it, which is much // faster in the typical case of rendering only a handful of quads. When modifying this value, // you must take care for the new amount of quads to fit in a single backend buffer (which is // not likely to be a problem). static const size_t MAX_QUAD_OVERLAYS = 1024; // Sets of commonly-(re)used shader defines. CShaderDefines defsOverlayLineNormal; CShaderDefines defsOverlayLineAlwaysVisible; CShaderDefines defsQuadOverlay; // Geometry for a unit sphere std::vector sphereVertexes; std::vector sphereIndexes; void GenerateSphere(); // Performs one-time setup. Called from CRenderer::Open, after graphics capabilities have // been detected. Note that no backend buffer must be created before this is called, since // the shader path and graphics capabilities are not guaranteed to be stable before this // point. void Initialize(); }; const float OverlayRenderer::OVERLAY_VOFFSET = 0.2f; OverlayRendererInternals::OverlayRendererInternals() : quadVertices(Renderer::Backend::IBuffer::Type::VERTEX, true), quadIndices(false) { quadAttributePos.format = Renderer::Backend::Format::R32G32B32_SFLOAT; quadVertices.AddAttribute(&quadAttributePos); quadAttributeColor.format = Renderer::Backend::Format::R8G8B8A8_UNORM; quadVertices.AddAttribute(&quadAttributeColor); quadAttributeUV.format = Renderer::Backend::Format::R16G16_SINT; quadVertices.AddAttribute(&quadAttributeUV); // Note that we're reusing the textured overlay line shader for the quad overlay rendering. This // is because their code is almost identical; the only difference is that for the quad overlays // we want to use a vertex color stream as opposed to an objectColor uniform. To this end, the // shader has been set up to switch between the two behaviours based on the USE_OBJECTCOLOR define. defsOverlayLineNormal.Add(str_USE_OBJECTCOLOR, str_1); defsOverlayLineAlwaysVisible.Add(str_USE_OBJECTCOLOR, str_1); defsOverlayLineAlwaysVisible.Add(str_IGNORE_LOS, str_1); } void OverlayRendererInternals::Initialize() { // Perform any initialization after graphics capabilities have been detected. Notably, // only at this point can we safely allocate backend buffer (in contrast to e.g. in the constructor), // because their creation depends on the shader path, which is not reliably set before this point. quadVertices.SetNumberOfVertices(MAX_QUAD_OVERLAYS * 4); quadVertices.Layout(); // allocate backing store quadIndices.SetNumberOfVertices(MAX_QUAD_OVERLAYS * 6); quadIndices.Layout(); // allocate backing store // Since the quads in the vertex array are independent and always consist of exactly 4 vertices per quad, the // indices are always the same; we can therefore fill in all the indices once and pretty much forget about // them. We then also no longer need its backing store, since we never change any indices afterwards. VertexArrayIterator index = quadIndices.GetIterator(); for (u16 i = 0; i < static_cast(MAX_QUAD_OVERLAYS); ++i) { *index++ = i * 4 + 0; *index++ = i * 4 + 1; *index++ = i * 4 + 2; *index++ = i * 4 + 2; *index++ = i * 4 + 3; *index++ = i * 4 + 0; } quadIndices.Upload(); quadIndices.FreeBackingStore(); } OverlayRenderer::OverlayRenderer() { m = new OverlayRendererInternals(); } OverlayRenderer::~OverlayRenderer() { delete m; } void OverlayRenderer::Initialize() { m->Initialize(); } void OverlayRenderer::Submit(SOverlayLine* line) { m->lines.push_back(line); } void OverlayRenderer::Submit(SOverlayTexturedLine* line) { // Simplify the rest of the code by guaranteeing non-empty lines if (line->m_Coords.empty()) return; m->texlines.push_back(line); } void OverlayRenderer::Submit(SOverlaySprite* overlay) { m->sprites.push_back(overlay); } void OverlayRenderer::Submit(SOverlayQuad* overlay) { m->quads.push_back(overlay); } void OverlayRenderer::Submit(SOverlaySphere* overlay) { m->spheres.push_back(overlay); } void OverlayRenderer::EndFrame() { m->lines.clear(); m->texlines.clear(); m->sprites.clear(); m->quads.clear(); m->spheres.clear(); // this should leave the capacity unchanged, which is okay since it // won't be very large or very variable // Empty the batch rendering data structures, but keep their key mappings around for the next frames for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it) { QuadBatchData& quadBatchData = (it->second); quadBatchData.m_Quads.clear(); quadBatchData.m_NumRenderQuads = 0; quadBatchData.m_IndicesBase = 0; } } void OverlayRenderer::PrepareForRendering() { PROFILE3("prepare overlays"); // This is where we should do something like sort the overlays by // color/sprite/etc for more efficient rendering for (size_t i = 0; i < m->texlines.size(); ++i) { SOverlayTexturedLine* line = m->texlines[i]; if (!line->m_RenderData) { line->m_RenderData = std::make_shared(); line->m_RenderData->Update(*line); // We assume the overlay line will get replaced by the caller // if terrain changes, so we don't need to detect that here and // call Update again. Also we assume the caller won't change // any of the parameters after first submitting the line. } } // Group quad overlays by their texture/mask combination for efficient rendering // TODO: consider doing this directly in Submit() for (size_t i = 0; i < m->quads.size(); ++i) { SOverlayQuad* const quad = m->quads[i]; QuadBatchKey textures(quad->m_Texture, quad->m_TextureMask); QuadBatchData& batchRenderData = m->quadBatchMap[textures]; // will create entry if it doesn't already exist // add overlay to list of quads batchRenderData.m_Quads.push_back(quad); } const CVector3D vOffset(0, OverlayRenderer::OVERLAY_VOFFSET, 0); // Write quad overlay vertices/indices to VA backing store VertexArrayIterator vertexPos = m->quadAttributePos.GetIterator(); VertexArrayIterator vertexColor = m->quadAttributeColor.GetIterator(); VertexArrayIterator vertexUV = m->quadAttributeUV.GetIterator(); size_t indicesIdx = 0; size_t totalNumQuads = 0; for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it) { QuadBatchData& batchRenderData = (it->second); batchRenderData.m_NumRenderQuads = 0; if (batchRenderData.m_Quads.empty()) continue; // Remember the current index into the (entire) indices array as our base offset for this batch batchRenderData.m_IndicesBase = indicesIdx; // points to the index where each iteration's vertices will be appended for (size_t i = 0; i < batchRenderData.m_Quads.size() && totalNumQuads < OverlayRendererInternals::MAX_QUAD_OVERLAYS; i++) { const SOverlayQuad* quad = batchRenderData.m_Quads[i]; const SColor4ub quadColor = quad->m_Color.AsSColor4ub(); *vertexPos++ = quad->m_Corners[0] + vOffset; *vertexPos++ = quad->m_Corners[1] + vOffset; *vertexPos++ = quad->m_Corners[2] + vOffset; *vertexPos++ = quad->m_Corners[3] + vOffset; (*vertexUV)[0] = 0; (*vertexUV)[1] = 0; ++vertexUV; (*vertexUV)[0] = 0; (*vertexUV)[1] = 1; ++vertexUV; (*vertexUV)[0] = 1; (*vertexUV)[1] = 1; ++vertexUV; (*vertexUV)[0] = 1; (*vertexUV)[1] = 0; ++vertexUV; *vertexColor++ = quadColor; *vertexColor++ = quadColor; *vertexColor++ = quadColor; *vertexColor++ = quadColor; indicesIdx += 6; totalNumQuads++; batchRenderData.m_NumRenderQuads++; } } m->quadVertices.Upload(); // don't free the backing store! we'll overwrite it on the next frame to save a reallocation. m->quadVertices.PrepareForRendering(); } void OverlayRenderer::RenderOverlaysBeforeWater( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { PROFILE3_GPU("overlays (before)"); GPU_SCOPED_LABEL(deviceCommandContext, "Render overlays before water"); for (SOverlayLine* line : m->lines) { if (line->m_Coords.empty()) continue; g_Renderer.GetDebugRenderer().DrawLine(line->m_Coords, line->m_Color, static_cast(line->m_Thickness)); } } void OverlayRenderer::RenderOverlaysAfterWater( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { PROFILE3_GPU("overlays (after)"); GPU_SCOPED_LABEL(deviceCommandContext, "Render overlays after water"); RenderTexturedOverlayLines(deviceCommandContext); RenderQuadOverlays(deviceCommandContext); RenderSphereOverlays(deviceCommandContext); } void OverlayRenderer::RenderTexturedOverlayLines(Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { if (m->texlines.empty()) return; CLOSTexture& los = g_Renderer.GetSceneRenderer().GetScene().GetLOSTexture(); CShaderTechniquePtr shaderTechTexLineNormal = GetOverlayLineShaderTechnique(m->defsOverlayLineNormal); if (shaderTechTexLineNormal) { Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = shaderTechTexLineNormal->GetGraphicsPipelineStateDesc(); pipelineStateDesc.depthStencilState.depthWriteEnabled = false; pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME) pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shaderTexLineNormal = shaderTechTexLineNormal->GetShader(); deviceCommandContext->SetTexture( shaderTexLineNormal->GetBindingSlot(str_losTex), los.GetTexture()); const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( shaderTexLineNormal->GetBindingSlot(str_transform), transform.AsFloatArray()); deviceCommandContext->SetUniform( shaderTexLineNormal->GetBindingSlot(str_losTransform), los.GetTextureMatrix()[0], los.GetTextureMatrix()[12]); // batch render only the non-always-visible overlay lines using the normal shader RenderTexturedOverlayLines(deviceCommandContext, shaderTexLineNormal, false); deviceCommandContext->EndPass(); } CShaderTechniquePtr shaderTechTexLineAlwaysVisible = GetOverlayLineShaderTechnique(m->defsOverlayLineAlwaysVisible); if (shaderTechTexLineAlwaysVisible) { Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = shaderTechTexLineAlwaysVisible->GetGraphicsPipelineStateDesc(); pipelineStateDesc.depthStencilState.depthWriteEnabled = false; pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME) pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shaderTexLineAlwaysVisible = shaderTechTexLineAlwaysVisible->GetShader(); // TODO: losTex and losTransform are unused in the always visible shader; see if these can be safely omitted deviceCommandContext->SetTexture( shaderTexLineAlwaysVisible->GetBindingSlot(str_losTex), los.GetTexture()); const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( shaderTexLineAlwaysVisible->GetBindingSlot(str_transform), transform.AsFloatArray()); deviceCommandContext->SetUniform( shaderTexLineAlwaysVisible->GetBindingSlot(str_losTransform), los.GetTextureMatrix()[0], los.GetTextureMatrix()[12]); // batch render only the always-visible overlay lines using the LoS-ignored shader RenderTexturedOverlayLines(deviceCommandContext, shaderTexLineAlwaysVisible, true); deviceCommandContext->EndPass(); } } void OverlayRenderer::RenderTexturedOverlayLines( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IShaderProgram* shader, bool alwaysVisible) { for (size_t i = 0; i < m->texlines.size(); ++i) { SOverlayTexturedLine* line = m->texlines[i]; // render only those lines matching the requested alwaysVisible status if (!line->m_RenderData || line->m_AlwaysVisible != alwaysVisible) continue; ENSURE(line->m_RenderData); line->m_RenderData->Render(deviceCommandContext, *line, shader); } } void OverlayRenderer::RenderQuadOverlays( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { if (m->quadBatchMap.empty()) return; CShaderTechniquePtr shaderTech = GetOverlayLineShaderTechnique(m->defsQuadOverlay); if (!shaderTech) return; Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = shaderTech->GetGraphicsPipelineStateDesc(); pipelineStateDesc.depthStencilState.depthWriteEnabled = false; pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME) pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = shaderTech->GetShader(); CLOSTexture& los = g_Renderer.GetSceneRenderer().GetScene().GetLOSTexture(); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_losTex), los.GetTexture()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_losTransform), los.GetTextureMatrix()[0], los.GetTextureMatrix()[12]); const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), transform.AsFloatArray()); m->quadVertices.UploadIfNeeded(deviceCommandContext); m->quadIndices.UploadIfNeeded(deviceCommandContext); const uint32_t vertexStride = m->quadVertices.GetStride(); const uint32_t firstVertexOffset = m->quadVertices.GetOffset() * vertexStride; const int32_t baseTexBindingSlot = shader->GetBindingSlot(str_baseTex); const int32_t maskTexBindingSlot = shader->GetBindingSlot(str_maskTex); for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it) { QuadBatchData& batchRenderData = it->second; const size_t batchNumQuads = batchRenderData.m_NumRenderQuads; if (batchNumQuads == 0) continue; const QuadBatchKey& maskPair = it->first; maskPair.m_Texture->UploadBackendTextureIfNeeded(deviceCommandContext); maskPair.m_TextureMask->UploadBackendTextureIfNeeded(deviceCommandContext); deviceCommandContext->SetTexture( baseTexBindingSlot, maskPair.m_Texture->GetBackendTexture()); deviceCommandContext->SetTexture( maskTexBindingSlot, maskPair.m_TextureMask->GetBackendTexture()); // TODO: move setting format out of the loop, we might want move the offset // to the index offset when it's supported. deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - m->quadAttributePos.format, firstVertexOffset + m->quadAttributePos.offset, vertexStride, 0); + m->quadAttributePos.format, firstVertexOffset + m->quadAttributePos.offset, vertexStride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::COLOR, - m->quadAttributeColor.format, firstVertexOffset + m->quadAttributeColor.offset, vertexStride, 0); + m->quadAttributeColor.format, firstVertexOffset + m->quadAttributeColor.offset, vertexStride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, - m->quadAttributeUV.format, firstVertexOffset + m->quadAttributeUV.offset, vertexStride, 0); + m->quadAttributeUV.format, firstVertexOffset + m->quadAttributeUV.offset, vertexStride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV1, - m->quadAttributeUV.format, firstVertexOffset + m->quadAttributeUV.offset, vertexStride, 0); + m->quadAttributeUV.format, firstVertexOffset + m->quadAttributeUV.offset, vertexStride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexBuffer(0, m->quadVertices.GetBuffer()); deviceCommandContext->SetIndexBuffer(m->quadIndices.GetBuffer()); deviceCommandContext->DrawIndexed(m->quadIndices.GetOffset() + batchRenderData.m_IndicesBase, batchNumQuads * 6, 0); g_Renderer.GetStats().m_DrawCalls++; g_Renderer.GetStats().m_OverlayTris += batchNumQuads*2; } deviceCommandContext->EndPass(); } void OverlayRenderer::RenderForegroundOverlays( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CCamera& viewCamera) { PROFILE3_GPU("overlays (fg)"); GPU_SCOPED_LABEL(deviceCommandContext, "Render foreground overlays"); const CVector3D right = -viewCamera.GetOrientation().GetLeft(); const CVector3D up = viewCamera.GetOrientation().GetUp(); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_foreground_overlay); Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = tech->GetGraphicsPipelineStateDesc(); pipelineStateDesc.depthStencilState.depthTestEnabled = false; pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME) pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = tech->GetShader(); const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), transform.AsFloatArray()); const CVector2D uvs[6] = { {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}, }; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, 0); + Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 1); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1); - deviceCommandContext->SetVertexBufferData(1, &uvs[0]); + deviceCommandContext->SetVertexBufferData( + 1, &uvs[0], std::size(uvs) * sizeof(uvs[0])); const int32_t baseTexBindingSlot = shader->GetBindingSlot(str_baseTex); const int32_t colorMulBindingSlot = shader->GetBindingSlot(str_colorMul); for (size_t i = 0; i < m->sprites.size(); ++i) { SOverlaySprite* sprite = m->sprites[i]; if (!i || sprite->m_Texture != m->sprites[i - 1]->m_Texture) { sprite->m_Texture->UploadBackendTextureIfNeeded(deviceCommandContext); deviceCommandContext->SetTexture( baseTexBindingSlot, sprite->m_Texture->GetBackendTexture()); } deviceCommandContext->SetUniform( colorMulBindingSlot, sprite->m_Color.AsFloatArray()); const CVector3D position[6] = { sprite->m_Position + right*sprite->m_X0 + up*sprite->m_Y0, sprite->m_Position + right*sprite->m_X1 + up*sprite->m_Y0, sprite->m_Position + right*sprite->m_X1 + up*sprite->m_Y1, sprite->m_Position + right*sprite->m_X0 + up*sprite->m_Y0, sprite->m_Position + right*sprite->m_X1 + up*sprite->m_Y1, sprite->m_Position + right*sprite->m_X0 + up*sprite->m_Y1 }; - deviceCommandContext->SetVertexBufferData(0, &position[0].X); + deviceCommandContext->SetVertexBufferData( + 0, &position[0].X, std::size(position) * sizeof(position[0])); deviceCommandContext->Draw(0, 6); g_Renderer.GetStats().m_DrawCalls++; g_Renderer.GetStats().m_OverlayTris += 2; } deviceCommandContext->EndPass(); } static void TessellateSphereFace(const CVector3D& a, u16 ai, const CVector3D& b, u16 bi, const CVector3D& c, u16 ci, std::vector& vertexes, std::vector& indexes, int level) { if (level == 0) { indexes.push_back(ai); indexes.push_back(bi); indexes.push_back(ci); } else { CVector3D d = (a + b).Normalized(); CVector3D e = (b + c).Normalized(); CVector3D f = (c + a).Normalized(); int di = vertexes.size() / 3; vertexes.push_back(d.X); vertexes.push_back(d.Y); vertexes.push_back(d.Z); int ei = vertexes.size() / 3; vertexes.push_back(e.X); vertexes.push_back(e.Y); vertexes.push_back(e.Z); int fi = vertexes.size() / 3; vertexes.push_back(f.X); vertexes.push_back(f.Y); vertexes.push_back(f.Z); TessellateSphereFace(a,ai, d,di, f,fi, vertexes, indexes, level-1); TessellateSphereFace(d,di, b,bi, e,ei, vertexes, indexes, level-1); TessellateSphereFace(f,fi, e,ei, c,ci, vertexes, indexes, level-1); TessellateSphereFace(d,di, e,ei, f,fi, vertexes, indexes, level-1); } } static void TessellateSphere(std::vector& vertexes, std::vector& indexes, int level) { /* Start with a tetrahedron, then tessellate */ float s = sqrtf(0.5f); #define VERT(a,b,c) vertexes.push_back(a); vertexes.push_back(b); vertexes.push_back(c); VERT(-s, 0, -s); VERT( s, 0, -s); VERT( s, 0, s); VERT(-s, 0, s); VERT( 0, -1, 0); VERT( 0, 1, 0); #define FACE(a,b,c) \ TessellateSphereFace( \ CVector3D(vertexes[a*3], vertexes[a*3+1], vertexes[a*3+2]), a, \ CVector3D(vertexes[b*3], vertexes[b*3+1], vertexes[b*3+2]), b, \ CVector3D(vertexes[c*3], vertexes[c*3+1], vertexes[c*3+2]), c, \ vertexes, indexes, level); FACE(0,4,1); FACE(1,4,2); FACE(2,4,3); FACE(3,4,0); FACE(1,5,0); FACE(2,5,1); FACE(3,5,2); FACE(0,5,3); #undef FACE #undef VERT } void OverlayRendererInternals::GenerateSphere() { if (sphereVertexes.empty()) TessellateSphere(sphereVertexes, sphereIndexes, 3); } void OverlayRenderer::RenderSphereOverlays( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { PROFILE3_GPU("overlays (spheres)"); if (m->spheres.empty()) return; Renderer::Backend::IShaderProgram* shader = nullptr; CShaderTechniquePtr tech; tech = g_Renderer.GetShaderManager().LoadEffect(str_overlay_solid); Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = tech->GetGraphicsPipelineStateDesc(); pipelineStateDesc.depthStencilState.depthWriteEnabled = false; pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); shader = tech->GetShader(); const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), transform.AsFloatArray()); m->GenerateSphere(); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, 0); + Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); - deviceCommandContext->SetVertexBufferData(0, m->sphereVertexes.data()); - deviceCommandContext->SetIndexBufferData(m->sphereIndexes.data()); + deviceCommandContext->SetVertexBufferData( + 0, m->sphereVertexes.data(), m->sphereVertexes.size() * sizeof(m->sphereVertexes[0])); + deviceCommandContext->SetIndexBufferData( + m->sphereIndexes.data(), m->sphereIndexes.size() * sizeof(m->sphereIndexes[0])); for (size_t i = 0; i < m->spheres.size(); ++i) { SOverlaySphere* sphere = m->spheres[i]; CMatrix3D instancingTransform; instancingTransform.SetIdentity(); instancingTransform.Scale( sphere->m_Radius, sphere->m_Radius, sphere->m_Radius); instancingTransform.Translate(sphere->m_Center); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_instancingTransform), instancingTransform.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_color), sphere->m_Color.AsFloatArray()); deviceCommandContext->DrawIndexed(0, m->sphereIndexes.size(), 0); g_Renderer.GetStats().m_DrawCalls++; g_Renderer.GetStats().m_OverlayTris = m->sphereIndexes.size()/3; } deviceCommandContext->EndPass(); } Index: ps/trunk/source/renderer/PatchRData.cpp =================================================================== --- ps/trunk/source/renderer/PatchRData.cpp (revision 26900) +++ ps/trunk/source/renderer/PatchRData.cpp (revision 26901) @@ -1,1543 +1,1557 @@ /* 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/PatchRData.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/LOSTexture.h" #include "graphics/Patch.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TextRenderer.h" #include "graphics/TextureManager.h" #include "lib/allocators/DynamicArena.h" #include "lib/allocators/STLAllocators.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/CStrInternStatic.h" #include "ps/Game.h" #include "ps/GameSetup/Config.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "renderer/AlphaMapCalculator.h" #include "renderer/DebugRenderer.h" #include "renderer/Renderer.h" #include "renderer/SceneRenderer.h" #include "renderer/TerrainRenderer.h" #include "renderer/WaterManager.h" #include "simulation2/components/ICmpWaterManager.h" #include "simulation2/Simulation2.h" #include #include #include const ssize_t BlendOffsets[9][2] = { { 0, -1 }, { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, 1 }, { 1, 1 }, { 1, 0 }, { 1, -1 }, { 0, 0 } }; CPatchRData::CPatchRData(CPatch* patch, CSimulation2* simulation) : m_Patch(patch), m_Simulation(simulation) { ENSURE(patch); Build(); } CPatchRData::~CPatchRData() = default; /** * Represents a blend for a single tile, texture and shape. */ struct STileBlend { CTerrainTextureEntry* m_Texture; int m_Priority; u16 m_TileMask; // bit n set if this blend contains neighbour tile BlendOffsets[n] struct DecreasingPriority { bool operator()(const STileBlend& a, const STileBlend& b) const { if (a.m_Priority > b.m_Priority) return true; if (a.m_Priority < b.m_Priority) return false; if (a.m_Texture && b.m_Texture) return a.m_Texture->GetTag() > b.m_Texture->GetTag(); return false; } }; struct CurrentTile { bool operator()(const STileBlend& a) const { return (a.m_TileMask & (1 << 8)) != 0; } }; }; /** * Represents the ordered collection of blends drawn on a particular tile. */ struct STileBlendStack { u8 i, j; std::vector blends; // back of vector is lowest-priority texture }; /** * Represents a batched collection of blends using the same texture. */ struct SBlendLayer { struct Tile { u8 i, j; u8 shape; }; CTerrainTextureEntry* m_Texture; std::vector m_Tiles; }; void CPatchRData::BuildBlends() { PROFILE3("build blends"); m_BlendSplats.clear(); std::vector blendVertices; std::vector blendIndices; CTerrain* terrain = m_Patch->m_Parent; std::vector blendStacks; blendStacks.reserve(PATCH_SIZE*PATCH_SIZE); std::vector blends; blends.reserve(9); // For each tile in patch .. for (ssize_t j = 0; j < PATCH_SIZE; ++j) { for (ssize_t i = 0; i < PATCH_SIZE; ++i) { ssize_t gx = m_Patch->m_X * PATCH_SIZE + i; ssize_t gz = m_Patch->m_Z * PATCH_SIZE + j; blends.clear(); // Compute a blend for every tile in the 3x3 square around this tile for (size_t n = 0; n < 9; ++n) { ssize_t ox = gx + BlendOffsets[n][1]; ssize_t oz = gz + BlendOffsets[n][0]; CMiniPatch* nmp = terrain->GetTile(ox, oz); if (!nmp) continue; STileBlend blend; blend.m_Texture = nmp->GetTextureEntry(); blend.m_Priority = nmp->GetPriority(); blend.m_TileMask = 1 << n; blends.push_back(blend); } // Sort the blends, highest priority first std::sort(blends.begin(), blends.end(), STileBlend::DecreasingPriority()); STileBlendStack blendStack; blendStack.i = i; blendStack.j = j; // Put the blends into the tile's stack, merging any adjacent blends with the same texture for (size_t k = 0; k < blends.size(); ++k) { if (!blendStack.blends.empty() && blendStack.blends.back().m_Texture == blends[k].m_Texture) blendStack.blends.back().m_TileMask |= blends[k].m_TileMask; else blendStack.blends.push_back(blends[k]); } // Remove blends that are after (i.e. lower priority than) the current tile // (including the current tile), since we don't want to render them on top of // the tile's base texture blendStack.blends.erase( std::find_if(blendStack.blends.begin(), blendStack.blends.end(), STileBlend::CurrentTile()), blendStack.blends.end()); blendStacks.push_back(blendStack); } } // Given the blend stack per tile, we want to batch together as many blends as possible. // Group them into a series of layers (each of which has a single texture): // (This is effectively a topological sort / linearisation of the partial order induced // by the per-tile stacks, preferring to make tiles with equal textures adjacent.) std::vector blendLayers; while (true) { if (!blendLayers.empty()) { // Try to grab as many tiles as possible that match our current layer, // from off the blend stacks of all the tiles CTerrainTextureEntry* tex = blendLayers.back().m_Texture; for (size_t k = 0; k < blendStacks.size(); ++k) { if (!blendStacks[k].blends.empty() && blendStacks[k].blends.back().m_Texture == tex) { SBlendLayer::Tile t = { blendStacks[k].i, blendStacks[k].j, (u8)blendStacks[k].blends.back().m_TileMask }; blendLayers.back().m_Tiles.push_back(t); blendStacks[k].blends.pop_back(); } // (We've already merged adjacent entries of the same texture in each stack, // so we don't need to bother looping to check the next entry in this stack again) } } // We've grabbed as many tiles as possible; now we need to start a new layer. // The new layer's texture could come from the back of any non-empty stack; // choose the longest stack as a heuristic to reduce the number of layers CTerrainTextureEntry* bestTex = NULL; size_t bestStackSize = 0; for (size_t k = 0; k < blendStacks.size(); ++k) { if (blendStacks[k].blends.size() > bestStackSize) { bestStackSize = blendStacks[k].blends.size(); bestTex = blendStacks[k].blends.back().m_Texture; } } // If all our stacks were empty, we're done if (bestStackSize == 0) break; // Otherwise add the new layer, then loop back and start filling it in SBlendLayer layer; layer.m_Texture = bestTex; blendLayers.push_back(layer); } // Now build outgoing splats m_BlendSplats.resize(blendLayers.size()); for (size_t k = 0; k < blendLayers.size(); ++k) { SSplat& splat = m_BlendSplats[k]; splat.m_IndexStart = blendIndices.size(); splat.m_Texture = blendLayers[k].m_Texture; for (size_t t = 0; t < blendLayers[k].m_Tiles.size(); ++t) { SBlendLayer::Tile& tile = blendLayers[k].m_Tiles[t]; AddBlend(blendVertices, blendIndices, tile.i, tile.j, tile.shape, splat.m_Texture); } splat.m_IndexCount = blendIndices.size() - splat.m_IndexStart; } // Release existing vertex buffer chunks m_VBBlends.Reset(); m_VBBlendIndices.Reset(); if (blendVertices.size()) { // Construct vertex buffer m_VBBlends = g_VBMan.AllocateChunk( sizeof(SBlendVertex), blendVertices.size(), Renderer::Backend::IBuffer::Type::VERTEX, false, nullptr, CVertexBufferManager::Group::TERRAIN); m_VBBlends->m_Owner->UpdateChunkVertices(m_VBBlends.Get(), &blendVertices[0]); // Update the indices to include the base offset of the vertex data for (size_t k = 0; k < blendIndices.size(); ++k) blendIndices[k] += static_cast(m_VBBlends->m_Index); m_VBBlendIndices = g_VBMan.AllocateChunk( sizeof(u16), blendIndices.size(), Renderer::Backend::IBuffer::Type::INDEX, false, nullptr, CVertexBufferManager::Group::TERRAIN); m_VBBlendIndices->m_Owner->UpdateChunkVertices(m_VBBlendIndices.Get(), &blendIndices[0]); } } void CPatchRData::AddBlend(std::vector& blendVertices, std::vector& blendIndices, u16 i, u16 j, u8 shape, CTerrainTextureEntry* texture) { CTerrain* terrain = m_Patch->m_Parent; ssize_t gx = m_Patch->m_X * PATCH_SIZE + i; ssize_t gz = m_Patch->m_Z * PATCH_SIZE + j; // uses the current neighbour texture BlendShape8 shape8; for (size_t m = 0; m < 8; ++m) shape8[m] = (shape & (1 << m)) ? 0 : 1; // calculate the required alphamap and the required rotation of the alphamap from blendshape unsigned int alphamapflags; int alphamap = CAlphaMapCalculator::Calculate(shape8, alphamapflags); // now actually render the blend tile (if we need one) if (alphamap == -1) return; float u0 = texture->m_TerrainAlpha->second.m_AlphaMapCoords[alphamap].u0; float u1 = texture->m_TerrainAlpha->second.m_AlphaMapCoords[alphamap].u1; float v0 = texture->m_TerrainAlpha->second.m_AlphaMapCoords[alphamap].v0; float v1 = texture->m_TerrainAlpha->second.m_AlphaMapCoords[alphamap].v1; if (alphamapflags & BLENDMAP_FLIPU) std::swap(u0, u1); if (alphamapflags & BLENDMAP_FLIPV) std::swap(v0, v1); int base = 0; if (alphamapflags & BLENDMAP_ROTATE90) base = 1; else if (alphamapflags & BLENDMAP_ROTATE180) base = 2; else if (alphamapflags & BLENDMAP_ROTATE270) base = 3; SBlendVertex vtx[4]; vtx[(base + 0) % 4].m_AlphaUVs[0] = u0; vtx[(base + 0) % 4].m_AlphaUVs[1] = v0; vtx[(base + 1) % 4].m_AlphaUVs[0] = u1; vtx[(base + 1) % 4].m_AlphaUVs[1] = v0; vtx[(base + 2) % 4].m_AlphaUVs[0] = u1; vtx[(base + 2) % 4].m_AlphaUVs[1] = v1; vtx[(base + 3) % 4].m_AlphaUVs[0] = u0; vtx[(base + 3) % 4].m_AlphaUVs[1] = v1; SBlendVertex dst; CVector3D normal; u16 index = static_cast(blendVertices.size()); terrain->CalcPosition(gx, gz, dst.m_Position); terrain->CalcNormal(gx, gz, normal); dst.m_Normal = normal; dst.m_AlphaUVs[0] = vtx[0].m_AlphaUVs[0]; dst.m_AlphaUVs[1] = vtx[0].m_AlphaUVs[1]; blendVertices.push_back(dst); terrain->CalcPosition(gx + 1, gz, dst.m_Position); terrain->CalcNormal(gx + 1, gz, normal); dst.m_Normal = normal; dst.m_AlphaUVs[0] = vtx[1].m_AlphaUVs[0]; dst.m_AlphaUVs[1] = vtx[1].m_AlphaUVs[1]; blendVertices.push_back(dst); terrain->CalcPosition(gx + 1, gz + 1, dst.m_Position); terrain->CalcNormal(gx + 1, gz + 1, normal); dst.m_Normal = normal; dst.m_AlphaUVs[0] = vtx[2].m_AlphaUVs[0]; dst.m_AlphaUVs[1] = vtx[2].m_AlphaUVs[1]; blendVertices.push_back(dst); terrain->CalcPosition(gx, gz + 1, dst.m_Position); terrain->CalcNormal(gx, gz + 1, normal); dst.m_Normal = normal; dst.m_AlphaUVs[0] = vtx[3].m_AlphaUVs[0]; dst.m_AlphaUVs[1] = vtx[3].m_AlphaUVs[1]; blendVertices.push_back(dst); bool dir = terrain->GetTriangulationDir(gx, gz); if (dir) { blendIndices.push_back(index+0); blendIndices.push_back(index+1); blendIndices.push_back(index+3); blendIndices.push_back(index+1); blendIndices.push_back(index+2); blendIndices.push_back(index+3); } else { blendIndices.push_back(index+0); blendIndices.push_back(index+1); blendIndices.push_back(index+2); blendIndices.push_back(index+2); blendIndices.push_back(index+3); blendIndices.push_back(index+0); } } void CPatchRData::BuildIndices() { PROFILE3("build indices"); CTerrain* terrain = m_Patch->m_Parent; ssize_t px = m_Patch->m_X * PATCH_SIZE; ssize_t pz = m_Patch->m_Z * PATCH_SIZE; // must have allocated some vertices before trying to build corresponding indices ENSURE(m_VBBase); // number of vertices in each direction in each patch ssize_t vsize=PATCH_SIZE+1; // PATCH_SIZE must be 2^8-2 or less to not overflow u16 indices buffer. Thankfully this is always true. ENSURE(vsize*vsize < 65536); std::vector indices; indices.reserve(PATCH_SIZE * PATCH_SIZE * 4); // release existing splats m_Splats.clear(); // build grid of textures on this patch std::vector textures; CTerrainTextureEntry* texgrid[PATCH_SIZE][PATCH_SIZE]; for (ssize_t j=0;jm_MiniPatches[j][i].GetTextureEntry(); texgrid[j][i]=tex; if (std::find(textures.begin(),textures.end(),tex)==textures.end()) { textures.push_back(tex); } } } // now build base splats from interior textures m_Splats.resize(textures.size()); // build indices for base splats size_t base=m_VBBase->m_Index; for (size_t k = 0; k < m_Splats.size(); ++k) { CTerrainTextureEntry* tex = textures[k]; SSplat& splat=m_Splats[k]; splat.m_Texture=tex; splat.m_IndexStart=indices.size(); for (ssize_t j = 0; j < PATCH_SIZE; j++) { for (ssize_t i = 0; i < PATCH_SIZE; i++) { if (texgrid[j][i] == tex) { bool dir = terrain->GetTriangulationDir(px+i, pz+j); if (dir) { indices.push_back(u16(((j+0)*vsize+(i+0))+base)); indices.push_back(u16(((j+0)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+0))+base)); indices.push_back(u16(((j+0)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+0))+base)); } else { indices.push_back(u16(((j+0)*vsize+(i+0))+base)); indices.push_back(u16(((j+0)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+0))+base)); indices.push_back(u16(((j+0)*vsize+(i+0))+base)); } } } } splat.m_IndexCount=indices.size()-splat.m_IndexStart; } // Release existing vertex buffer chunk m_VBBaseIndices.Reset(); ENSURE(indices.size()); // Construct vertex buffer m_VBBaseIndices = g_VBMan.AllocateChunk( sizeof(u16), indices.size(), Renderer::Backend::IBuffer::Type::INDEX, false, nullptr, CVertexBufferManager::Group::TERRAIN); m_VBBaseIndices->m_Owner->UpdateChunkVertices(m_VBBaseIndices.Get(), &indices[0]); } void CPatchRData::BuildVertices() { PROFILE3("build vertices"); // create both vertices and lighting colors // number of vertices in each direction in each patch ssize_t vsize = PATCH_SIZE + 1; std::vector vertices; vertices.resize(vsize * vsize); // get index of this patch ssize_t px = m_Patch->m_X; ssize_t pz = m_Patch->m_Z; CTerrain* terrain = m_Patch->m_Parent; // build vertices for (ssize_t j = 0; j < vsize; ++j) { for (ssize_t i = 0; i < vsize; ++i) { ssize_t ix = px * PATCH_SIZE + i; ssize_t iz = pz * PATCH_SIZE + j; ssize_t v = j * vsize + i; // calculate vertex data terrain->CalcPosition(ix, iz, vertices[v].m_Position); CVector3D normal; terrain->CalcNormal(ix, iz, normal); vertices[v].m_Normal = normal; } } // upload to vertex buffer if (!m_VBBase) { m_VBBase = g_VBMan.AllocateChunk( sizeof(SBaseVertex), vsize * vsize, Renderer::Backend::IBuffer::Type::VERTEX, false, nullptr, CVertexBufferManager::Group::TERRAIN); } m_VBBase->m_Owner->UpdateChunkVertices(m_VBBase.Get(), &vertices[0]); } void CPatchRData::BuildSide(std::vector& vertices, CPatchSideFlags side) { ssize_t vsize = PATCH_SIZE + 1; CTerrain* terrain = m_Patch->m_Parent; CmpPtr cmpWaterManager(*m_Simulation, SYSTEM_ENTITY); for (ssize_t k = 0; k < vsize; k++) { ssize_t gx = m_Patch->m_X * PATCH_SIZE; ssize_t gz = m_Patch->m_Z * PATCH_SIZE; switch (side) { case CPATCH_SIDE_NEGX: gz += k; break; case CPATCH_SIDE_POSX: gx += PATCH_SIZE; gz += PATCH_SIZE-k; break; case CPATCH_SIDE_NEGZ: gx += PATCH_SIZE-k; break; case CPATCH_SIDE_POSZ: gz += PATCH_SIZE; gx += k; break; } CVector3D pos; terrain->CalcPosition(gx, gz, pos); // Clamp the height to the water level float waterHeight = 0.f; if (cmpWaterManager) waterHeight = cmpWaterManager->GetExactWaterLevel(pos.X, pos.Z); pos.Y = std::max(pos.Y, waterHeight); SSideVertex v0, v1; v0.m_Position = pos; v1.m_Position = pos; v1.m_Position.Y = 0; if (k == 0) { vertices.emplace_back(v1); vertices.emplace_back(v0); } if (k > 0) { const size_t lastIndex = vertices.size() - 1; vertices.emplace_back(v1); vertices.emplace_back(vertices[lastIndex]); vertices.emplace_back(v0); vertices.emplace_back(v1); if (k + 1 < vsize) { vertices.emplace_back(v1); vertices.emplace_back(v0); } } } } void CPatchRData::BuildSides() { PROFILE3("build sides"); std::vector sideVertices; int sideFlags = m_Patch->GetSideFlags(); // If no sides are enabled, we don't need to do anything if (!sideFlags) return; // For each side, generate a tristrip by adding a vertex at ground/water // level and a vertex underneath at height 0. if (sideFlags & CPATCH_SIDE_NEGX) BuildSide(sideVertices, CPATCH_SIDE_NEGX); if (sideFlags & CPATCH_SIDE_POSX) BuildSide(sideVertices, CPATCH_SIDE_POSX); if (sideFlags & CPATCH_SIDE_NEGZ) BuildSide(sideVertices, CPATCH_SIDE_NEGZ); if (sideFlags & CPATCH_SIDE_POSZ) BuildSide(sideVertices, CPATCH_SIDE_POSZ); if (sideVertices.empty()) return; if (!m_VBSides) { m_VBSides = g_VBMan.AllocateChunk( sizeof(SSideVertex), sideVertices.size(), Renderer::Backend::IBuffer::Type::VERTEX, false, nullptr, CVertexBufferManager::Group::DEFAULT); } m_VBSides->m_Owner->UpdateChunkVertices(m_VBSides.Get(), &sideVertices[0]); } void CPatchRData::Build() { BuildVertices(); BuildSides(); BuildIndices(); BuildBlends(); BuildWater(); } void CPatchRData::Update(CSimulation2* simulation) { m_Simulation = simulation; if (m_UpdateFlags!=0) { // TODO,RC 11/04/04 - need to only rebuild necessary bits of renderdata rather // than everything; it's complicated slightly because the blends are dependent // on both vertex and index data BuildVertices(); BuildSides(); BuildIndices(); BuildBlends(); BuildWater(); m_UpdateFlags=0; } } // To minimise the cost of memory allocations, everything used for computing // batches uses a arena allocator. (All allocations are short-lived so we can // just throw away the whole arena at the end of each frame.) using Arena = Allocators::DynamicArena<1 * MiB>; // std::map types with appropriate arena allocators and default comparison operator template using PooledBatchMap = std::map, ProxyAllocator, Arena>>; // Equivalent to "m[k]", when it returns a arena-allocated std::map (since we can't // use the default constructor in that case) template typename M::mapped_type& PooledMapGet(M& m, const typename M::key_type& k, Arena& arena) { return m.insert(std::make_pair(k, typename M::mapped_type(typename M::mapped_type::key_compare(), typename M::mapped_type::allocator_type(arena)) )).first->second; } // Equivalent to "m[k]", when it returns a std::pair of arena-allocated std::vectors template typename M::mapped_type& PooledPairGet(M& m, const typename M::key_type& k, Arena& arena) { return m.insert(std::make_pair(k, std::make_pair( typename M::mapped_type::first_type(typename M::mapped_type::first_type::allocator_type(arena)), typename M::mapped_type::second_type(typename M::mapped_type::second_type::allocator_type(arena)) ))).first->second; } // Each multidraw batch has a list of index counts, and a list of pointers-to-first-indexes using BatchElements = std::pair>, std::vector>>; // Group batches by index buffer using IndexBufferBatches = PooledBatchMap; // Group batches by vertex buffer using VertexBufferBatches = PooledBatchMap; // Group batches by texture using TextureBatches = PooledBatchMap; // Group batches by shaders. using ShaderTechniqueBatches = PooledBatchMap, TextureBatches>; void CPatchRData::RenderBases( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const std::vector& patches, const CShaderDefines& context, ShadowMap* shadow) { PROFILE3("render terrain bases"); GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain bases"); Arena arena; ShaderTechniqueBatches batches(ShaderTechniqueBatches::key_compare(), (ShaderTechniqueBatches::allocator_type(arena))); PROFILE_START("compute batches"); // Collect all the patches' base splats into their appropriate batches for (size_t i = 0; i < patches.size(); ++i) { CPatchRData* patch = patches[i]; for (size_t j = 0; j < patch->m_Splats.size(); ++j) { SSplat& splat = patch->m_Splats[j]; const CMaterial& material = splat.m_Texture->GetMaterial(); if (material.GetShaderEffect().empty()) { LOGERROR("Terrain renderer failed to load shader effect.\n"); continue; } BatchElements& batch = PooledPairGet( PooledMapGet( PooledMapGet( PooledMapGet(batches, std::make_pair(material.GetShaderEffect(), material.GetShaderDefines()), arena), splat.m_Texture, arena ), patch->m_VBBase->m_Owner, arena ), patch->m_VBBaseIndices->m_Owner, arena ); batch.first.push_back(splat.m_IndexCount); batch.second.push_back(patch->m_VBBaseIndices->m_Index + splat.m_IndexStart); } } PROFILE_END("compute batches"); // Render each batch for (ShaderTechniqueBatches::iterator itTech = batches.begin(); itTech != batches.end(); ++itTech) { CShaderDefines defines = context; defines.SetMany(itTech->first.second); CShaderTechniquePtr techBase = g_Renderer.GetShaderManager().LoadEffect( itTech->first.first, defines); const int numPasses = techBase->GetNumPasses(); for (int pass = 0; pass < numPasses; ++pass) { deviceCommandContext->SetGraphicsPipelineState( techBase->GetGraphicsPipelineStateDesc(pass)); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = techBase->GetShader(pass); TerrainRenderer::PrepareShader(deviceCommandContext, shader, shadow); const int32_t baseTexBindingSlot = shader->GetBindingSlot(str_baseTex); const int32_t textureTransformBindingSlot = shader->GetBindingSlot(str_textureTransform); TextureBatches& textureBatches = itTech->second; for (TextureBatches::iterator itt = textureBatches.begin(); itt != textureBatches.end(); ++itt) { if (!itt->first->GetMaterial().GetSamplers().empty()) { const CMaterial::SamplersVector& samplers = itt->first->GetMaterial().GetSamplers(); for(const CMaterial::TextureSampler& samp : samplers) samp.Sampler->UploadBackendTextureIfNeeded(deviceCommandContext); for(const CMaterial::TextureSampler& samp : samplers) { deviceCommandContext->SetTexture( shader->GetBindingSlot(samp.Name), samp.Sampler->GetBackendTexture()); } itt->first->GetMaterial().GetStaticUniforms().BindUniforms( deviceCommandContext, shader); float c = itt->first->GetTextureMatrix()[0]; float ms = itt->first->GetTextureMatrix()[8]; deviceCommandContext->SetUniform( textureTransformBindingSlot, c, ms); } else { deviceCommandContext->SetTexture( baseTexBindingSlot, g_Renderer.GetTextureManager().GetErrorTexture()->GetBackendTexture()); } for (VertexBufferBatches::iterator itv = itt->second.begin(); itv != itt->second.end(); ++itv) { itv->first->UploadIfNeeded(deviceCommandContext); const uint32_t stride = sizeof(SBaseVertex); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, Renderer::Backend::Format::R32G32B32_SFLOAT, - offsetof(SBaseVertex, m_Position), stride, 0); + offsetof(SBaseVertex, m_Position), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::NORMAL, Renderer::Backend::Format::R32G32B32_SFLOAT, - offsetof(SBaseVertex, m_Normal), stride, 0); + offsetof(SBaseVertex, m_Normal), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, Renderer::Backend::Format::R32G32B32_SFLOAT, - offsetof(SBaseVertex, m_Position), stride, 0); + offsetof(SBaseVertex, m_Position), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexBuffer(0, itv->first->GetBuffer()); for (IndexBufferBatches::iterator it = itv->second.begin(); it != itv->second.end(); ++it) { it->first->UploadIfNeeded(deviceCommandContext); deviceCommandContext->SetIndexBuffer(it->first->GetBuffer()); BatchElements& batch = it->second; for (size_t i = 0; i < batch.first.size(); ++i) deviceCommandContext->DrawIndexed(batch.second[i], batch.first[i], 0); g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_TerrainTris += std::accumulate(batch.first.begin(), batch.first.end(), 0) / 3; } } } deviceCommandContext->EndPass(); } } } /** * Helper structure for RenderBlends. */ struct SBlendBatch { SBlendBatch(Arena& arena) : m_Batches(VertexBufferBatches::key_compare(), VertexBufferBatches::allocator_type(arena)) { } CTerrainTextureEntry* m_Texture; CShaderTechniquePtr m_ShaderTech; VertexBufferBatches m_Batches; }; /** * Helper structure for RenderBlends. */ struct SBlendStackItem { SBlendStackItem(CVertexBuffer::VBChunk* v, CVertexBuffer::VBChunk* i, const std::vector& s, Arena& arena) : vertices(v), indices(i), splats(s.begin(), s.end(), SplatStack::allocator_type(arena)) { } using SplatStack = std::vector>; CVertexBuffer::VBChunk* vertices; CVertexBuffer::VBChunk* indices; SplatStack splats; }; void CPatchRData::RenderBlends( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const std::vector& patches, const CShaderDefines& context, ShadowMap* shadow) { PROFILE3("render terrain blends"); GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain blends"); Arena arena; using BatchesStack = std::vector>; BatchesStack batches((BatchesStack::allocator_type(arena))); CShaderDefines contextBlend = context; contextBlend.Add(str_BLEND, str_1); PROFILE_START("compute batches"); // Reserve an arbitrary size that's probably big enough in most cases, // to avoid heavy reallocations batches.reserve(256); using BlendStacks = std::vector>; BlendStacks blendStacks((BlendStacks::allocator_type(arena))); blendStacks.reserve(patches.size()); // Extract all the blend splats from each patch for (size_t i = 0; i < patches.size(); ++i) { CPatchRData* patch = patches[i]; if (!patch->m_BlendSplats.empty()) { blendStacks.push_back(SBlendStackItem(patch->m_VBBlends.Get(), patch->m_VBBlendIndices.Get(), patch->m_BlendSplats, arena)); // Reverse the splats so the first to be rendered is at the back of the list std::reverse(blendStacks.back().splats.begin(), blendStacks.back().splats.end()); } } // Rearrange the collection of splats to be grouped by texture, preserving // order of splats within each patch: // (This is exactly the same algorithm used in CPatchRData::BuildBlends, // but applied to patch-sized splats rather than to tile-sized splats; // see that function for comments on the algorithm.) while (true) { if (!batches.empty()) { CTerrainTextureEntry* tex = batches.back().m_Texture; for (size_t k = 0; k < blendStacks.size(); ++k) { SBlendStackItem::SplatStack& splats = blendStacks[k].splats; if (!splats.empty() && splats.back().m_Texture == tex) { CVertexBuffer::VBChunk* vertices = blendStacks[k].vertices; CVertexBuffer::VBChunk* indices = blendStacks[k].indices; BatchElements& batch = PooledPairGet(PooledMapGet(batches.back().m_Batches, vertices->m_Owner, arena), indices->m_Owner, arena); batch.first.push_back(splats.back().m_IndexCount); batch.second.push_back(indices->m_Index + splats.back().m_IndexStart); splats.pop_back(); } } } CTerrainTextureEntry* bestTex = NULL; size_t bestStackSize = 0; for (size_t k = 0; k < blendStacks.size(); ++k) { SBlendStackItem::SplatStack& splats = blendStacks[k].splats; if (splats.size() > bestStackSize) { bestStackSize = splats.size(); bestTex = splats.back().m_Texture; } } if (bestStackSize == 0) break; SBlendBatch layer(arena); layer.m_Texture = bestTex; if (!bestTex->GetMaterial().GetSamplers().empty()) { CShaderDefines defines = contextBlend; defines.SetMany(bestTex->GetMaterial().GetShaderDefines()); layer.m_ShaderTech = g_Renderer.GetShaderManager().LoadEffect( bestTex->GetMaterial().GetShaderEffect(), defines); } batches.push_back(layer); } PROFILE_END("compute batches"); CVertexBuffer* lastVB = nullptr; Renderer::Backend::IShaderProgram* previousShader = nullptr; for (BatchesStack::iterator itTechBegin = batches.begin(), itTechEnd = batches.begin(); itTechBegin != batches.end(); itTechBegin = itTechEnd) { while (itTechEnd != batches.end() && itTechEnd->m_ShaderTech == itTechBegin->m_ShaderTech) ++itTechEnd; const CShaderTechniquePtr& techBase = itTechBegin->m_ShaderTech; const int numPasses = techBase->GetNumPasses(); for (int pass = 0; pass < numPasses; ++pass) { Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = techBase->GetGraphicsPipelineStateDesc(pass); pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = techBase->GetShader(pass); TerrainRenderer::PrepareShader(deviceCommandContext, shader, shadow); Renderer::Backend::ITexture* lastBlendTex = nullptr; const int32_t baseTexBindingSlot = shader->GetBindingSlot(str_baseTex); const int32_t blendTexBindingSlot = shader->GetBindingSlot(str_blendTex); const int32_t textureTransformBindingSlot = shader->GetBindingSlot(str_textureTransform); for (BatchesStack::iterator itt = itTechBegin; itt != itTechEnd; ++itt) { if (itt->m_Texture->GetMaterial().GetSamplers().empty()) continue; if (itt->m_Texture) { const CMaterial::SamplersVector& samplers = itt->m_Texture->GetMaterial().GetSamplers(); for (const CMaterial::TextureSampler& samp : samplers) samp.Sampler->UploadBackendTextureIfNeeded(deviceCommandContext); for (const CMaterial::TextureSampler& samp : samplers) { deviceCommandContext->SetTexture( shader->GetBindingSlot(samp.Name), samp.Sampler->GetBackendTexture()); } Renderer::Backend::ITexture* currentBlendTex = itt->m_Texture->m_TerrainAlpha->second.m_CompositeAlphaMap.get(); if (currentBlendTex != lastBlendTex) { deviceCommandContext->SetTexture( blendTexBindingSlot, currentBlendTex); lastBlendTex = currentBlendTex; } itt->m_Texture->GetMaterial().GetStaticUniforms().BindUniforms(deviceCommandContext, shader); float c = itt->m_Texture->GetTextureMatrix()[0]; float ms = itt->m_Texture->GetTextureMatrix()[8]; deviceCommandContext->SetUniform( textureTransformBindingSlot, c, ms); } else { deviceCommandContext->SetTexture( baseTexBindingSlot, g_Renderer.GetTextureManager().GetErrorTexture()->GetBackendTexture()); } for (VertexBufferBatches::iterator itv = itt->m_Batches.begin(); itv != itt->m_Batches.end(); ++itv) { // Rebind the VB only if it changed since the last batch if (itv->first != lastVB || shader != previousShader) { lastVB = itv->first; previousShader = shader; itv->first->UploadIfNeeded(deviceCommandContext); const uint32_t stride = sizeof(SBlendVertex); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, Renderer::Backend::Format::R32G32B32_SFLOAT, - offsetof(SBlendVertex, m_Position), stride, 0); + offsetof(SBlendVertex, m_Position), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::NORMAL, Renderer::Backend::Format::R32G32B32_SFLOAT, - offsetof(SBlendVertex, m_Normal), stride, 0); + offsetof(SBlendVertex, m_Normal), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, Renderer::Backend::Format::R32G32B32_SFLOAT, - offsetof(SBlendVertex, m_Position), stride, 0); + offsetof(SBlendVertex, m_Position), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV1, Renderer::Backend::Format::R32G32_SFLOAT, - offsetof(SBlendVertex, m_AlphaUVs), stride, 0); + offsetof(SBlendVertex, m_AlphaUVs), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexBuffer(0, itv->first->GetBuffer()); } for (IndexBufferBatches::iterator it = itv->second.begin(); it != itv->second.end(); ++it) { it->first->UploadIfNeeded(deviceCommandContext); deviceCommandContext->SetIndexBuffer(it->first->GetBuffer()); BatchElements& batch = it->second; for (size_t i = 0; i < batch.first.size(); ++i) deviceCommandContext->DrawIndexed(batch.second[i], batch.first[i], 0); g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_BlendSplats++; g_Renderer.m_Stats.m_TerrainTris += std::accumulate(batch.first.begin(), batch.first.end(), 0) / 3; } } } deviceCommandContext->EndPass(); } } } void CPatchRData::RenderStreams( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const std::vector& patches, const bool bindPositionAsTexCoord) { PROFILE3("render terrain streams"); // Each batch has a list of index counts, and a list of pointers-to-first-indexes using StreamBatchElements = std::pair, std::vector>; // Group batches by index buffer using StreamIndexBufferBatches = std::map; // Group batches by vertex buffer using StreamVertexBufferBatches = std::map; StreamVertexBufferBatches batches; PROFILE_START("compute batches"); // Collect all the patches into their appropriate batches for (const CPatchRData* patch : patches) { StreamBatchElements& batch = batches[patch->m_VBBase->m_Owner][patch->m_VBBaseIndices->m_Owner]; batch.first.push_back(patch->m_VBBaseIndices->m_Count); batch.second.push_back(patch->m_VBBaseIndices->m_Index); } PROFILE_END("compute batches"); const uint32_t stride = sizeof(SBaseVertex); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, Renderer::Backend::Format::R32G32B32_SFLOAT, - offsetof(SBaseVertex, m_Position), stride, 0); + offsetof(SBaseVertex, m_Position), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); if (bindPositionAsTexCoord) { deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, Renderer::Backend::Format::R32G32B32_SFLOAT, - offsetof(SBaseVertex, m_Position), stride, 0); + offsetof(SBaseVertex, m_Position), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); } // Render each batch for (const std::pair& streamBatch : batches) { streamBatch.first->UploadIfNeeded(deviceCommandContext); deviceCommandContext->SetVertexBuffer(0, streamBatch.first->GetBuffer()); for (const std::pair& batchIndexBuffer : streamBatch.second) { batchIndexBuffer.first->UploadIfNeeded(deviceCommandContext); deviceCommandContext->SetIndexBuffer(batchIndexBuffer.first->GetBuffer()); const StreamBatchElements& batch = batchIndexBuffer.second; for (size_t i = 0; i < batch.first.size(); ++i) deviceCommandContext->DrawIndexed(batch.second[i], batch.first[i], 0); g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_TerrainTris += std::accumulate(batch.first.begin(), batch.first.end(), 0) / 3; } } } void CPatchRData::RenderOutline() { CTerrain* terrain = m_Patch->m_Parent; ssize_t gx = m_Patch->m_X * PATCH_SIZE; ssize_t gz = m_Patch->m_Z * PATCH_SIZE; CVector3D pos; std::vector line; for (ssize_t i = 0, j = 0; i <= PATCH_SIZE; ++i) { terrain->CalcPosition(gx + i, gz + j, pos); line.push_back(pos); } for (ssize_t i = PATCH_SIZE, j = 1; j <= PATCH_SIZE; ++j) { terrain->CalcPosition(gx + i, gz + j, pos); line.push_back(pos); } for (ssize_t i = PATCH_SIZE-1, j = PATCH_SIZE; i >= 0; --i) { terrain->CalcPosition(gx + i, gz + j, pos); line.push_back(pos); } for (ssize_t i = 0, j = PATCH_SIZE-1; j >= 0; --j) { terrain->CalcPosition(gx + i, gz + j, pos); line.push_back(pos); } g_Renderer.GetDebugRenderer().DrawLine(line, CColor(0.0f, 0.0f, 1.0f, 1.0f), 0.1f); } void CPatchRData::RenderSides( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const std::vector& patches) { PROFILE3("render terrain sides"); GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain sides"); if (patches.empty()) return; const uint32_t stride = sizeof(SSideVertex); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, Renderer::Backend::Format::R32G32B32_SFLOAT, - offsetof(SSideVertex, m_Position), stride, 0); + offsetof(SSideVertex, m_Position), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); CVertexBuffer* lastVB = nullptr; for (CPatchRData* patch : patches) { ENSURE(patch->m_UpdateFlags == 0); if (!patch->m_VBSides) continue; if (lastVB != patch->m_VBSides->m_Owner) { lastVB = patch->m_VBSides->m_Owner; patch->m_VBSides->m_Owner->UploadIfNeeded(deviceCommandContext); deviceCommandContext->SetVertexBuffer(0, patch->m_VBSides->m_Owner->GetBuffer()); } deviceCommandContext->Draw(patch->m_VBSides->m_Index, (GLsizei)patch->m_VBSides->m_Count); // bump stats g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_TerrainTris += patch->m_VBSides->m_Count / 3; } } void CPatchRData::RenderPriorities(CTextRenderer& textRenderer) { CTerrain* terrain = m_Patch->m_Parent; const CCamera& camera = *(g_Game->GetView()->GetCamera()); for (ssize_t j = 0; j < PATCH_SIZE; ++j) { for (ssize_t i = 0; i < PATCH_SIZE; ++i) { ssize_t gx = m_Patch->m_X * PATCH_SIZE + i; ssize_t gz = m_Patch->m_Z * PATCH_SIZE + j; CVector3D pos; terrain->CalcPosition(gx, gz, pos); // Move a bit towards the center of the tile pos.X += TERRAIN_TILE_SIZE/4.f; pos.Z += TERRAIN_TILE_SIZE/4.f; float x, y; camera.GetScreenCoordinates(pos, x, y); textRenderer.PrintfAt(x, y, L"%d", m_Patch->m_MiniPatches[j][i].Priority); } } } // // Water build and rendering // // Build vertex buffer for water vertices over our patch void CPatchRData::BuildWater() { PROFILE3("build water"); // Number of vertices in each direction in each patch ENSURE(PATCH_SIZE % water_cell_size == 0); m_VBWater.Reset(); m_VBWaterIndices.Reset(); m_VBWaterShore.Reset(); m_VBWaterIndicesShore.Reset(); m_WaterBounds.SetEmpty(); // We need to use this to access the water manager or we may not have the // actual values but some compiled-in defaults CmpPtr cmpWaterManager(*m_Simulation, SYSTEM_ENTITY); if (!cmpWaterManager) return; // Build data for water std::vector water_vertex_data; std::vector water_indices; u16 water_index_map[PATCH_SIZE+1][PATCH_SIZE+1]; memset(water_index_map, 0xFF, sizeof(water_index_map)); // Build data for shore std::vector water_vertex_data_shore; std::vector water_indices_shore; u16 water_shore_index_map[PATCH_SIZE+1][PATCH_SIZE+1]; memset(water_shore_index_map, 0xFF, sizeof(water_shore_index_map)); const WaterManager& waterManager = g_Renderer.GetSceneRenderer().GetWaterManager(); CPatch* patch = m_Patch; CTerrain* terrain = patch->m_Parent; ssize_t mapSize = terrain->GetVerticesPerSide(); // Top-left coordinates of our patch. ssize_t px = m_Patch->m_X * PATCH_SIZE; ssize_t pz = m_Patch->m_Z * PATCH_SIZE; // To whoever implements different water heights, this is a TODO: water height) float waterHeight = cmpWaterManager->GetExactWaterLevel(0.0f,0.0f); // The 4 points making a water tile. int moves[4][2] = { {0, 0}, {water_cell_size, 0}, {0, water_cell_size}, {water_cell_size, water_cell_size} }; // Where to look for when checking for water for shore tiles. int check[10][2] = { {0, 0}, {water_cell_size, 0}, {water_cell_size*2, 0}, {0, water_cell_size}, {0, water_cell_size*2}, {water_cell_size, water_cell_size}, {water_cell_size*2, water_cell_size*2}, {-water_cell_size, 0}, {0, -water_cell_size}, {-water_cell_size, -water_cell_size} }; // build vertices, uv, and shader varying for (ssize_t z = 0; z < PATCH_SIZE; z += water_cell_size) { for (ssize_t x = 0; x < PATCH_SIZE; x += water_cell_size) { // Check that this tile is close to water bool nearWater = false; for (size_t test = 0; test < 10; ++test) if (terrain->GetVertexGroundLevel(x + px + check[test][0], z + pz + check[test][1]) < waterHeight) nearWater = true; if (!nearWater) continue; // This is actually lying and I should call CcmpTerrain /*if (!terrain->IsOnMap(x+x1, z+z1) && !terrain->IsOnMap(x+x1, z+z1 + water_cell_size) && !terrain->IsOnMap(x+x1 + water_cell_size, z+z1) && !terrain->IsOnMap(x+x1 + water_cell_size, z+z1 + water_cell_size)) continue;*/ for (int i = 0; i < 4; ++i) { if (water_index_map[z+moves[i][1]][x+moves[i][0]] != 0xFFFF) continue; ssize_t xx = x + px + moves[i][0]; ssize_t zz = z + pz + moves[i][1]; SWaterVertex vertex; terrain->CalcPosition(xx,zz, vertex.m_Position); float depth = waterHeight - vertex.m_Position.Y; vertex.m_Position.Y = waterHeight; m_WaterBounds += vertex.m_Position; vertex.m_WaterData = CVector2D(waterManager.m_WindStrength[xx + zz*mapSize], depth); water_index_map[z+moves[i][1]][x+moves[i][0]] = static_cast(water_vertex_data.size()); water_vertex_data.push_back(vertex); } water_indices.push_back(water_index_map[z + moves[2][1]][x + moves[2][0]]); water_indices.push_back(water_index_map[z + moves[0][1]][x + moves[0][0]]); water_indices.push_back(water_index_map[z + moves[1][1]][x + moves[1][0]]); water_indices.push_back(water_index_map[z + moves[1][1]][x + moves[1][0]]); water_indices.push_back(water_index_map[z + moves[3][1]][x + moves[3][0]]); water_indices.push_back(water_index_map[z + moves[2][1]][x + moves[2][0]]); // Check id this tile is partly over land. // If so add a square over the terrain. This is necessary to render waves that go on shore. if (terrain->GetVertexGroundLevel(x+px, z+pz) < waterHeight && terrain->GetVertexGroundLevel(x+px + water_cell_size, z+pz) < waterHeight && terrain->GetVertexGroundLevel(x+px, z+pz+water_cell_size) < waterHeight && terrain->GetVertexGroundLevel(x+px + water_cell_size, z+pz+water_cell_size) < waterHeight) continue; for (int i = 0; i < 4; ++i) { if (water_shore_index_map[z+moves[i][1]][x+moves[i][0]] != 0xFFFF) continue; ssize_t xx = x + px + moves[i][0]; ssize_t zz = z + pz + moves[i][1]; SWaterVertex vertex; terrain->CalcPosition(xx,zz, vertex.m_Position); vertex.m_Position.Y += 0.02f; m_WaterBounds += vertex.m_Position; vertex.m_WaterData = CVector2D(0.0f, -5.0f); water_shore_index_map[z+moves[i][1]][x+moves[i][0]] = static_cast(water_vertex_data_shore.size()); water_vertex_data_shore.push_back(vertex); } if (terrain->GetTriangulationDir(x + px, z + pz)) { water_indices_shore.push_back(water_shore_index_map[z + moves[2][1]][x + moves[2][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[0][1]][x + moves[0][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[1][1]][x + moves[1][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[1][1]][x + moves[1][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[3][1]][x + moves[3][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[2][1]][x + moves[2][0]]); } else { water_indices_shore.push_back(water_shore_index_map[z + moves[3][1]][x + moves[3][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[2][1]][x + moves[2][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[0][1]][x + moves[0][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[3][1]][x + moves[3][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[0][1]][x + moves[0][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[1][1]][x + moves[1][0]]); } } } // No vertex buffers if no data generated if (!water_indices.empty()) { m_VBWater = g_VBMan.AllocateChunk( sizeof(SWaterVertex), water_vertex_data.size(), Renderer::Backend::IBuffer::Type::VERTEX, false, nullptr, CVertexBufferManager::Group::WATER); m_VBWater->m_Owner->UpdateChunkVertices(m_VBWater.Get(), &water_vertex_data[0]); m_VBWaterIndices = g_VBMan.AllocateChunk( sizeof(GLushort), water_indices.size(), Renderer::Backend::IBuffer::Type::INDEX, false, nullptr, CVertexBufferManager::Group::WATER); m_VBWaterIndices->m_Owner->UpdateChunkVertices(m_VBWaterIndices.Get(), &water_indices[0]); } if (!water_indices_shore.empty()) { m_VBWaterShore = g_VBMan.AllocateChunk( sizeof(SWaterVertex), water_vertex_data_shore.size(), Renderer::Backend::IBuffer::Type::VERTEX, false, nullptr, CVertexBufferManager::Group::WATER); m_VBWaterShore->m_Owner->UpdateChunkVertices(m_VBWaterShore.Get(), &water_vertex_data_shore[0]); // Construct indices buffer m_VBWaterIndicesShore = g_VBMan.AllocateChunk( sizeof(GLushort), water_indices_shore.size(), Renderer::Backend::IBuffer::Type::INDEX, false, nullptr, CVertexBufferManager::Group::WATER); m_VBWaterIndicesShore->m_Owner->UpdateChunkVertices(m_VBWaterIndicesShore.Get(), &water_indices_shore[0]); } } void CPatchRData::RenderWaterSurface( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const bool bindWaterData) { ASSERT(m_UpdateFlags == 0); if (!m_VBWater) return; m_VBWater->m_Owner->UploadIfNeeded(deviceCommandContext); m_VBWaterIndices->m_Owner->UploadIfNeeded(deviceCommandContext); const uint32_t stride = sizeof(SWaterVertex); const uint32_t firstVertexOffset = m_VBWater->m_Index * stride; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, Renderer::Backend::Format::R32G32B32_SFLOAT, - firstVertexOffset + offsetof(SWaterVertex, m_Position), stride, 0); + firstVertexOffset + offsetof(SWaterVertex, m_Position), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); if (bindWaterData) { deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV1, Renderer::Backend::Format::R32G32_SFLOAT, - firstVertexOffset + offsetof(SWaterVertex, m_WaterData), stride, 0); + firstVertexOffset + offsetof(SWaterVertex, m_WaterData), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); } deviceCommandContext->SetVertexBuffer(0, m_VBWater->m_Owner->GetBuffer()); deviceCommandContext->SetIndexBuffer(m_VBWaterIndices->m_Owner->GetBuffer()); deviceCommandContext->DrawIndexed(m_VBWaterIndices->m_Index, m_VBWaterIndices->m_Count, 0); g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_WaterTris += m_VBWaterIndices->m_Count / 3; } void CPatchRData::RenderWaterShore( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { ASSERT(m_UpdateFlags == 0); if (!m_VBWaterShore) return; m_VBWaterShore->m_Owner->UploadIfNeeded(deviceCommandContext); m_VBWaterIndicesShore->m_Owner->UploadIfNeeded(deviceCommandContext); const uint32_t stride = sizeof(SWaterVertex); const uint32_t firstVertexOffset = m_VBWaterShore->m_Index * stride; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, Renderer::Backend::Format::R32G32B32_SFLOAT, - firstVertexOffset + offsetof(SWaterVertex, m_Position), stride, 0); + firstVertexOffset + offsetof(SWaterVertex, m_Position), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV1, Renderer::Backend::Format::R32G32_SFLOAT, - firstVertexOffset + offsetof(SWaterVertex, m_WaterData), stride, 0); + firstVertexOffset + offsetof(SWaterVertex, m_WaterData), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexBuffer(0, m_VBWaterShore->m_Owner->GetBuffer()); deviceCommandContext->SetIndexBuffer(m_VBWaterIndicesShore->m_Owner->GetBuffer()); deviceCommandContext->DrawIndexed(m_VBWaterIndicesShore->m_Index, m_VBWaterIndicesShore->m_Count, 0); g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_WaterTris += m_VBWaterIndicesShore->m_Count / 3; } Index: ps/trunk/source/renderer/PostprocManager.cpp =================================================================== --- ps/trunk/source/renderer/PostprocManager.cpp (revision 26900) +++ ps/trunk/source/renderer/PostprocManager.cpp (revision 26901) @@ -1,692 +1,708 @@ /* 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/PostprocManager.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/ShaderManager.h" #include "lib/bits.h" #include "maths/MathUtil.h" #include "ps/ConfigDB.h" #include "ps/CLogger.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "renderer/backend/IDevice.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "tools/atlas/GameInterface/GameLoop.h" CPostprocManager::CPostprocManager() : m_IsInitialized(false), m_PostProcEffect(L"default"), m_WhichBuffer(true), m_Sharpness(0.3f), m_UsingMultisampleBuffer(false), m_MultisampleCount(0) { } CPostprocManager::~CPostprocManager() { Cleanup(); } bool CPostprocManager::IsEnabled() const { return g_RenderingOptions.GetPostProc() && g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB && g_VideoMode.GetBackendDevice()->IsTextureFormatSupported( Renderer::Backend::Format::D24_S8); } void CPostprocManager::Cleanup() { if (!m_IsInitialized) // Only cleanup if previously used return; m_CaptureFramebuffer.reset(); m_PingFramebuffer.reset(); m_PongFramebuffer.reset(); m_ColorTex1.reset(); m_ColorTex2.reset(); m_DepthTex.reset(); for (BlurScale& scale : m_BlurScales) { for (BlurScale::Step& step : scale.steps) { step.framebuffer.reset(); step.texture.reset(); } } } void CPostprocManager::Initialize() { if (m_IsInitialized) return; const uint32_t maxSamples = g_VideoMode.GetBackendDevice()->GetCapabilities().maxSampleCount; const uint32_t possibleSampleCounts[] = {2, 4, 8, 16}; std::copy_if( std::begin(possibleSampleCounts), std::end(possibleSampleCounts), std::back_inserter(m_AllowedSampleCounts), [maxSamples](const uint32_t sampleCount) { return sampleCount <= maxSamples; } ); // The screen size starts out correct and then must be updated with Resize() m_Width = g_Renderer.GetWidth(); m_Height = g_Renderer.GetHeight(); RecreateBuffers(); m_IsInitialized = true; // Once we have initialised the buffers, we can update the techniques. UpdateAntiAliasingTechnique(); UpdateSharpeningTechnique(); UpdateSharpnessFactor(); // This might happen after the map is loaded and the effect chosen SetPostEffect(m_PostProcEffect); } void CPostprocManager::Resize() { m_Width = g_Renderer.GetWidth(); m_Height = g_Renderer.GetHeight(); // If the buffers were intialized, recreate them to the new size. if (m_IsInitialized) RecreateBuffers(); } void CPostprocManager::RecreateBuffers() { Cleanup(); Renderer::Backend::IDevice* backendDevice = g_VideoMode.GetBackendDevice(); #define GEN_BUFFER_RGBA(name, w, h) \ name = backendDevice->CreateTexture2D( \ "PostProc" #name, Renderer::Backend::Format::R8G8B8A8_UNORM, w, h, \ Renderer::Backend::Sampler::MakeDefaultSampler( \ Renderer::Backend::Sampler::Filter::LINEAR, \ Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)); // Two fullscreen ping-pong textures. GEN_BUFFER_RGBA(m_ColorTex1, m_Width, m_Height); GEN_BUFFER_RGBA(m_ColorTex2, m_Width, m_Height); // Textures for several blur sizes. It would be possible to reuse // m_BlurTex2b, thus avoiding the need for m_BlurTex4b and m_BlurTex8b, though given // that these are fairly small it's probably not worth complicating the coordinates passed // to the blur helper functions. uint32_t width = m_Width / 2, height = m_Height / 2; for (BlurScale& scale : m_BlurScales) { for (BlurScale::Step& step : scale.steps) { GEN_BUFFER_RGBA(step.texture, width, height); step.framebuffer = backendDevice->CreateFramebuffer("BlurScaleSteoFramebuffer", step.texture.get(), nullptr); } width /= 2; height /= 2; } #undef GEN_BUFFER_RGBA // Allocate the Depth/Stencil texture. m_DepthTex = backendDevice->CreateTexture2D("PostPRocDepthTexture", Renderer::Backend::Format::D24_S8, m_Width, m_Height, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)); // Set up the framebuffers with some initial textures. m_CaptureFramebuffer = backendDevice->CreateFramebuffer("PostprocCaptureFramebuffer", m_ColorTex1.get(), m_DepthTex.get(), g_VideoMode.GetBackendDevice()->GetCurrentBackbuffer()->GetClearColor()); m_PingFramebuffer = backendDevice->CreateFramebuffer("PostprocPingFramebuffer", m_ColorTex1.get(), nullptr); m_PongFramebuffer = backendDevice->CreateFramebuffer("PostprocPongFramebuffer", m_ColorTex2.get(), nullptr); if (!m_CaptureFramebuffer || !m_PingFramebuffer || !m_PongFramebuffer) { LOGWARNING("Failed to create postproc framebuffers"); g_RenderingOptions.SetPostProc(false); } if (m_UsingMultisampleBuffer) { DestroyMultisampleBuffer(); CreateMultisampleBuffer(); } } void CPostprocManager::ApplyBlurDownscale2x( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IFramebuffer* framebuffer, Renderer::Backend::ITexture* inTex, int inWidth, int inHeight) { deviceCommandContext->SetFramebuffer(framebuffer); // Get bloom shader with instructions to simply copy texels. CShaderDefines defines; defines.Add(str_BLOOM_NOP, str_1); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines); deviceCommandContext->SetGraphicsPipelineState( tech->GetGraphicsPipelineStateDesc()); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = tech->GetShader(); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_renderedTex), inTex); const SViewPort oldVp = g_Renderer.GetViewport(); const SViewPort vp = { 0, 0, inWidth / 2, inHeight / 2 }; g_Renderer.SetViewport(vp); // TODO: remove the fullscreen quad drawing duplication. float quadVerts[] = { 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f }; float quadTex[] = { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 0); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 1); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1); - deviceCommandContext->SetVertexBufferData(0, quadVerts); - deviceCommandContext->SetVertexBufferData(1, quadTex); + deviceCommandContext->SetVertexBufferData( + 0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0])); + deviceCommandContext->SetVertexBufferData( + 1, quadTex, std::size(quadTex) * sizeof(quadTex[0])); deviceCommandContext->Draw(0, 6); g_Renderer.SetViewport(oldVp); deviceCommandContext->EndPass(); } void CPostprocManager::ApplyBlurGauss( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::ITexture* inTex, Renderer::Backend::ITexture* tempTex, Renderer::Backend::IFramebuffer* tempFramebuffer, Renderer::Backend::IFramebuffer* outFramebuffer, int inWidth, int inHeight) { deviceCommandContext->SetFramebuffer(tempFramebuffer); // Get bloom shader, for a horizontal Gaussian blur pass. CShaderDefines defines2; defines2.Add(str_BLOOM_PASS_H, str_1); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines2); deviceCommandContext->SetGraphicsPipelineState( tech->GetGraphicsPipelineStateDesc()); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = tech->GetShader(); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_renderedTex), inTex); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_texSize), inWidth, inHeight); const SViewPort oldVp = g_Renderer.GetViewport(); const SViewPort vp = { 0, 0, inWidth, inHeight }; g_Renderer.SetViewport(vp); float quadVerts[] = { 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f }; float quadTex[] = { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 0); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 1); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1); - deviceCommandContext->SetVertexBufferData(0, quadVerts); - deviceCommandContext->SetVertexBufferData(1, quadTex); + deviceCommandContext->SetVertexBufferData( + 0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0])); + deviceCommandContext->SetVertexBufferData( + 1, quadTex, std::size(quadTex) * sizeof(quadTex[0])); deviceCommandContext->Draw(0, 6); g_Renderer.SetViewport(oldVp); deviceCommandContext->EndPass(); deviceCommandContext->SetFramebuffer(outFramebuffer); // Get bloom shader, for a vertical Gaussian blur pass. CShaderDefines defines3; defines3.Add(str_BLOOM_PASS_V, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines3); deviceCommandContext->SetGraphicsPipelineState( tech->GetGraphicsPipelineStateDesc()); deviceCommandContext->BeginPass(); shader = tech->GetShader(); // Our input texture to the shader is the output of the horizontal pass. deviceCommandContext->SetTexture( shader->GetBindingSlot(str_renderedTex), tempTex); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_texSize), inWidth, inHeight); g_Renderer.SetViewport(vp); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 0); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 1); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1); - deviceCommandContext->SetVertexBufferData(0, quadVerts); - deviceCommandContext->SetVertexBufferData(1, quadTex); + deviceCommandContext->SetVertexBufferData( + 0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0])); + deviceCommandContext->SetVertexBufferData( + 1, quadTex, std::size(quadTex) * sizeof(quadTex[0])); deviceCommandContext->Draw(0, 6); g_Renderer.SetViewport(oldVp); deviceCommandContext->EndPass(); } void CPostprocManager::ApplyBlur( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { uint32_t width = m_Width, height = m_Height; Renderer::Backend::ITexture* previousTexture = (m_WhichBuffer ? m_ColorTex1 : m_ColorTex2).get(); for (BlurScale& scale : m_BlurScales) { ApplyBlurDownscale2x(deviceCommandContext, scale.steps[0].framebuffer.get(), previousTexture, width, height); width /= 2; height /= 2; ApplyBlurGauss(deviceCommandContext, scale.steps[0].texture.get(), scale.steps[1].texture.get(), scale.steps[1].framebuffer.get(), scale.steps[0].framebuffer.get(), width, height); } } void CPostprocManager::CaptureRenderOutput( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { ENSURE(m_IsInitialized); // Leaves m_PingFbo selected for rendering; m_WhichBuffer stays true at this point. if (m_UsingMultisampleBuffer) deviceCommandContext->SetFramebuffer(m_MultisampleFramebuffer.get()); else deviceCommandContext->SetFramebuffer(m_CaptureFramebuffer.get()); m_WhichBuffer = true; } void CPostprocManager::ReleaseRenderOutput( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { ENSURE(m_IsInitialized); GPU_SCOPED_LABEL(deviceCommandContext, "Copy postproc to backbuffer"); // We blit to the backbuffer from the previous active buffer. deviceCommandContext->BlitFramebuffer( deviceCommandContext->GetDevice()->GetCurrentBackbuffer(), (m_WhichBuffer ? m_PingFramebuffer : m_PongFramebuffer).get()); deviceCommandContext->SetFramebuffer( deviceCommandContext->GetDevice()->GetCurrentBackbuffer()); } void CPostprocManager::ApplyEffect( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CShaderTechniquePtr& shaderTech, int pass) { // select the other FBO for rendering deviceCommandContext->SetFramebuffer( (m_WhichBuffer ? m_PongFramebuffer : m_PingFramebuffer).get()); deviceCommandContext->SetGraphicsPipelineState( shaderTech->GetGraphicsPipelineStateDesc(pass)); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = shaderTech->GetShader(pass); // Use the textures from the current FBO as input to the shader. // We also bind a bunch of other textures and parameters, but since // this only happens once per frame the overhead is negligible. deviceCommandContext->SetTexture( shader->GetBindingSlot(str_renderedTex), m_WhichBuffer ? m_ColorTex1.get() : m_ColorTex2.get()); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_depthTex), m_DepthTex.get()); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_blurTex2), m_BlurScales[0].steps[0].texture.get()); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_blurTex4), m_BlurScales[1].steps[0].texture.get()); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_blurTex8), m_BlurScales[2].steps[0].texture.get()); deviceCommandContext->SetUniform(shader->GetBindingSlot(str_width), m_Width); deviceCommandContext->SetUniform(shader->GetBindingSlot(str_height), m_Height); deviceCommandContext->SetUniform(shader->GetBindingSlot(str_zNear), m_NearPlane); deviceCommandContext->SetUniform(shader->GetBindingSlot(str_zFar), m_FarPlane); deviceCommandContext->SetUniform(shader->GetBindingSlot(str_sharpness), m_Sharpness); deviceCommandContext->SetUniform(shader->GetBindingSlot(str_brightness), g_LightEnv.m_Brightness); deviceCommandContext->SetUniform(shader->GetBindingSlot(str_hdr), g_LightEnv.m_Contrast); deviceCommandContext->SetUniform(shader->GetBindingSlot(str_saturation), g_LightEnv.m_Saturation); deviceCommandContext->SetUniform(shader->GetBindingSlot(str_bloom), g_LightEnv.m_Bloom); float quadVerts[] = { 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f }; float quadTex[] = { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 0); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, - Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 1); + Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1); - deviceCommandContext->SetVertexBufferData(0, quadVerts); - deviceCommandContext->SetVertexBufferData(1, quadTex); + deviceCommandContext->SetVertexBufferData( + 0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0])); + deviceCommandContext->SetVertexBufferData( + 1, quadTex, std::size(quadTex) * sizeof(quadTex[0])); deviceCommandContext->Draw(0, 6); deviceCommandContext->EndPass(); m_WhichBuffer = !m_WhichBuffer; } void CPostprocManager::ApplyPostproc( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { ENSURE(m_IsInitialized); // Don't do anything if we are using the default effect and no AA. const bool hasEffects = m_PostProcEffect != L"default"; const bool hasARB = g_VideoMode.GetBackend() == CVideoMode::Backend::GL_ARB; const bool hasAA = m_AATech && !hasARB; const bool hasSharp = m_SharpTech && !hasARB; if (!hasEffects && !hasAA && !hasSharp) return; GPU_SCOPED_LABEL(deviceCommandContext, "Render postproc"); if (hasEffects) { // First render blur textures. Note that this only happens ONLY ONCE, before any effects are applied! // (This may need to change depending on future usage, however that will have a fps hit) ApplyBlur(deviceCommandContext); for (int pass = 0; pass < m_PostProcTech->GetNumPasses(); ++pass) ApplyEffect(deviceCommandContext, m_PostProcTech, pass); } if (hasAA) { for (int pass = 0; pass < m_AATech->GetNumPasses(); ++pass) ApplyEffect(deviceCommandContext, m_AATech, pass); } if (hasSharp) { for (int pass = 0; pass < m_SharpTech->GetNumPasses(); ++pass) ApplyEffect(deviceCommandContext, m_SharpTech, pass); } } // Generate list of available effect-sets std::vector CPostprocManager::GetPostEffects() { std::vector effects; const VfsPath folder(L"shaders/effects/postproc/"); VfsPaths pathnames; if (vfs::GetPathnames(g_VFS, folder, 0, pathnames) < 0) LOGERROR("Error finding Post effects in '%s'", folder.string8()); for (const VfsPath& path : pathnames) if (path.Extension() == L".xml") effects.push_back(path.Basename().string()); // Add the default "null" effect to the list. effects.push_back(L"default"); sort(effects.begin(), effects.end()); return effects; } void CPostprocManager::SetPostEffect(const CStrW& name) { if (m_IsInitialized) { if (name != L"default") { CStrW n = L"postproc/" + name; m_PostProcTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern(n.ToUTF8())); } } m_PostProcEffect = name; } void CPostprocManager::UpdateAntiAliasingTechnique() { if (g_VideoMode.GetBackend() == CVideoMode::Backend::GL_ARB || !m_IsInitialized) return; CStr newAAName; CFG_GET_VAL("antialiasing", newAAName); if (m_AAName == newAAName) return; m_AAName = newAAName; m_AATech.reset(); if (m_UsingMultisampleBuffer) { m_UsingMultisampleBuffer = false; DestroyMultisampleBuffer(); } // We have to hardcode names in the engine, because anti-aliasing // techinques strongly depend on the graphics pipeline. // We might use enums in future though. const CStr msaaPrefix = "msaa"; if (m_AAName == "fxaa") { m_AATech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern("fxaa")); } else if (m_AAName.size() > msaaPrefix.size() && m_AAName.substr(0, msaaPrefix.size()) == msaaPrefix) { // We don't want to enable MSAA in Atlas, because it uses wxWidgets and its canvas. if (g_AtlasGameLoop && g_AtlasGameLoop->running) return; if (!g_VideoMode.GetBackendDevice()->GetCapabilities().multisampling || m_AllowedSampleCounts.empty()) { LOGWARNING("MSAA is unsupported."); return; } std::stringstream ss(m_AAName.substr(msaaPrefix.size())); ss >> m_MultisampleCount; if (std::find(std::begin(m_AllowedSampleCounts), std::end(m_AllowedSampleCounts), m_MultisampleCount) == std::end(m_AllowedSampleCounts)) { m_MultisampleCount = std::min(4u, g_VideoMode.GetBackendDevice()->GetCapabilities().maxSampleCount); LOGWARNING("Wrong MSAA sample count: %s.", m_AAName.EscapeToPrintableASCII().c_str()); } m_UsingMultisampleBuffer = true; CreateMultisampleBuffer(); } } void CPostprocManager::UpdateSharpeningTechnique() { if (g_VideoMode.GetBackend() == CVideoMode::Backend::GL_ARB || !m_IsInitialized) return; CStr newSharpName; CFG_GET_VAL("sharpening", newSharpName); if (m_SharpName == newSharpName) return; m_SharpName = newSharpName; m_SharpTech.reset(); if (m_SharpName == "cas") { m_SharpTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern(m_SharpName)); } } void CPostprocManager::UpdateSharpnessFactor() { CFG_GET_VAL("sharpness", m_Sharpness); } void CPostprocManager::SetDepthBufferClipPlanes(float nearPlane, float farPlane) { m_NearPlane = nearPlane; m_FarPlane = farPlane; } void CPostprocManager::CreateMultisampleBuffer() { Renderer::Backend::IDevice* backendDevice = g_VideoMode.GetBackendDevice(); m_MultisampleColorTex = backendDevice->CreateTexture("PostProcColorMS", Renderer::Backend::ITexture::Type::TEXTURE_2D_MULTISAMPLE, Renderer::Backend::Format::R8G8B8A8_UNORM, m_Width, m_Height, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, m_MultisampleCount); // Allocate the Depth/Stencil texture. m_MultisampleDepthTex = backendDevice->CreateTexture("PostProcDepthMS", Renderer::Backend::ITexture::Type::TEXTURE_2D_MULTISAMPLE, Renderer::Backend::Format::D24_S8, m_Width, m_Height, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, m_MultisampleCount); // Set up the framebuffers with some initial textures. m_MultisampleFramebuffer = backendDevice->CreateFramebuffer("PostprocMultisampleFramebuffer", m_MultisampleColorTex.get(), m_MultisampleDepthTex.get(), g_VideoMode.GetBackendDevice()->GetCurrentBackbuffer()->GetClearColor()); if (!m_MultisampleFramebuffer) { LOGERROR("Failed to create postproc multisample framebuffer"); m_UsingMultisampleBuffer = false; DestroyMultisampleBuffer(); } } void CPostprocManager::DestroyMultisampleBuffer() { if (m_UsingMultisampleBuffer) return; m_MultisampleFramebuffer.reset(); m_MultisampleColorTex.reset(); m_MultisampleDepthTex.reset(); } bool CPostprocManager::IsMultisampleEnabled() const { return m_UsingMultisampleBuffer; } void CPostprocManager::ResolveMultisampleFramebuffer( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { if (!m_UsingMultisampleBuffer) return; GPU_SCOPED_LABEL(deviceCommandContext, "Resolve postproc multisample"); deviceCommandContext->BlitFramebuffer( m_PingFramebuffer.get(), m_MultisampleFramebuffer.get()); deviceCommandContext->SetFramebuffer(m_PingFramebuffer.get()); } Index: ps/trunk/source/renderer/SilhouetteRenderer.cpp =================================================================== --- ps/trunk/source/renderer/SilhouetteRenderer.cpp (revision 26900) +++ ps/trunk/source/renderer/SilhouetteRenderer.cpp (revision 26901) @@ -1,516 +1,518 @@ /* 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 "SilhouetteRenderer.h" #include "graphics/Camera.h" #include "graphics/HFTracer.h" #include "graphics/Model.h" #include "graphics/Patch.h" #include "graphics/ShaderManager.h" #include "maths/MathUtil.h" #include "ps/CStrInternStatic.h" #include "ps/Profile.h" #include "renderer/DebugRenderer.h" #include "renderer/Renderer.h" #include "renderer/Scene.h" #include extern int g_xres, g_yres; // For debugging static const bool g_DisablePreciseIntersections = false; SilhouetteRenderer::SilhouetteRenderer() { m_DebugEnabled = false; } void SilhouetteRenderer::AddOccluder(CPatch* patch) { m_SubmittedPatchOccluders.push_back(patch); } void SilhouetteRenderer::AddOccluder(CModel* model) { m_SubmittedModelOccluders.push_back(model); } void SilhouetteRenderer::AddCaster(CModel* model) { m_SubmittedModelCasters.push_back(model); } /* * Silhouettes are the solid-colored versions of units that are rendered when * standing behind a building or terrain, so the player won't lose them. * * The rendering is done in CRenderer::RenderSilhouettes, by rendering the * units (silhouette casters) and buildings/terrain (silhouette occluders) * in an extra pass using depth and stencil buffers. It's very inefficient to * render those objects when they're not actually going to contribute to a * silhouette. * * This class is responsible for finding the subset of casters/occluders * that might contribute to a silhouette and will need to be rendered. * * The algorithm is largely based on sweep-and-prune for detecting intersection * along a single axis: * * First we compute the 2D screen-space bounding box of every occluder, and * their minimum distance from the camera. We also compute the screen-space * position of each caster (approximating them as points, which is not perfect * but almost always good enough). * * We split each occluder's screen-space bounds into a left ('in') edge and * right ('out') edge. We put those edges plus the caster points into a list, * and sort by x coordinate. * * Then we walk through the list, maintaining an active set of occluders. * An 'in' edge will add an occluder to the set, an 'out' edge will remove it. * When we reach a caster point, the active set contains all the occluders that * intersect it in x. We do a quick test of y and depth coordinates against * each occluder in the set. If they pass that test, we do a more precise ray * vs bounding box test (for model occluders) or ray vs patch (for terrain * occluders) to see if we really need to render that caster and occluder. * * Performance relies on the active set being quite small. Given the game's * typical occluder sizes and camera angles, this works out okay. * * We have to do precise ray/patch intersection tests for terrain, because * if we just used the patch's bounding box, pretty much every unit would * be seen as intersecting the patch it's standing on. * * We store screen-space coordinates as 14-bit integers (0..16383) because * that lets us pack and sort the edge/point list efficiently. */ static const u16 g_MaxCoord = 1 << 14; static const u16 g_HalfMaxCoord = g_MaxCoord / 2; struct Occluder { CRenderableObject* renderable; bool isPatch; u16 x0, y0, x1, y1; float z; bool rendered; }; struct Caster { CModel* model; u16 x, y; float z; bool rendered; }; enum { EDGE_IN, EDGE_OUT, POINT }; // Entry is essentially: // struct Entry { // u16 id; // index into occluders array // u16 type : 2; // u16 x : 14; // }; // where x is in the most significant bits, so that sorting as a uint32_t // is the same as sorting by x. To avoid worrying about endianness and the // compiler's ability to handle bitfields efficiently, we use uint32_t instead // of the actual struct. typedef uint32_t Entry; static Entry EntryCreate(int type, u16 id, u16 x) { return (x << 18) | (type << 16) | id; } static int EntryGetId(Entry e) { return e & 0xffff; } static int EntryGetType(Entry e) { return (e >> 16) & 3; } struct ActiveList { std::vector m_Ids; void Add(u16 id) { m_Ids.push_back(id); } void Remove(u16 id) { ssize_t sz = m_Ids.size(); for (ssize_t i = sz-1; i >= 0; --i) { if (m_Ids[i] == id) { m_Ids[i] = m_Ids[sz-1]; m_Ids.pop_back(); return; } } debug_warn(L"Failed to find id"); } }; static void ComputeScreenBounds(Occluder& occluder, const CBoundingBoxAligned& bounds, CMatrix3D& proj) { u16 x0 = std::numeric_limits::max(); u16 y0 = std::numeric_limits::max(); u16 x1 = std::numeric_limits::min(); u16 y1 = std::numeric_limits::min(); float z0 = std::numeric_limits::max(); for (size_t ix = 0; ix <= 1; ++ix) { for (size_t iy = 0; iy <= 1; ++iy) { for (size_t iz = 0; iz <= 1; ++iz) { CVector4D svec = proj.Transform(CVector4D(bounds[ix].X, bounds[iy].Y, bounds[iz].Z, 1.0f)); x0 = std::min(x0, static_cast(g_HalfMaxCoord + static_cast(g_HalfMaxCoord * svec.X / svec.W))); y0 = std::min(y0, static_cast(g_HalfMaxCoord + static_cast(g_HalfMaxCoord * svec.Y / svec.W))); x1 = std::max(x1, static_cast(g_HalfMaxCoord + static_cast(g_HalfMaxCoord * svec.X / svec.W))); y1 = std::max(y1, static_cast(g_HalfMaxCoord + static_cast(g_HalfMaxCoord * svec.Y / svec.W))); z0 = std::min(z0, svec.Z / svec.W); } } } // TODO: there must be a quicker way to do this than to test every vertex, // given the symmetry of the bounding box occluder.x0 = Clamp(x0, std::numeric_limits::min(), static_cast(g_MaxCoord - 1)); occluder.y0 = Clamp(y0, std::numeric_limits::min(), static_cast(g_MaxCoord - 1)); occluder.x1 = Clamp(x1, std::numeric_limits::min(), static_cast(g_MaxCoord - 1)); occluder.y1 = Clamp(y1, std::numeric_limits::min(), static_cast(g_MaxCoord - 1)); occluder.z = z0; } static void ComputeScreenPos(Caster& caster, const CVector3D& pos, CMatrix3D& proj) { CVector4D svec = proj.Transform(CVector4D(pos.X, pos.Y, pos.Z, 1.0f)); u16 x = g_HalfMaxCoord + static_cast(g_HalfMaxCoord * svec.X / svec.W); u16 y = g_HalfMaxCoord + static_cast(g_HalfMaxCoord * svec.Y / svec.W); caster.x = Clamp(x, std::numeric_limits::min(), static_cast(g_MaxCoord - 1)); caster.y = Clamp(y, std::numeric_limits::min(), static_cast(g_MaxCoord - 1)); caster.z = svec.Z / svec.W; } void SilhouetteRenderer::ComputeSubmissions(const CCamera& camera) { PROFILE3("compute silhouettes"); m_DebugBounds.clear(); m_DebugRects.clear(); m_DebugSpheres.clear(); m_VisiblePatchOccluders.clear(); m_VisibleModelOccluders.clear(); m_VisibleModelCasters.clear(); std::vector occluders; std::vector casters; std::vector entries; occluders.reserve(m_SubmittedModelOccluders.size() + m_SubmittedPatchOccluders.size()); casters.reserve(m_SubmittedModelCasters.size()); entries.reserve((m_SubmittedModelOccluders.size() + m_SubmittedPatchOccluders.size()) * 2 + m_SubmittedModelCasters.size()); CMatrix3D proj = camera.GetViewProjection(); // Bump the positions of unit casters upwards a bit, so they're not always // detected as intersecting the terrain they're standing on CVector3D posOffset(0.0f, 0.1f, 0.0f); #if 0 // For debugging ray-patch intersections - casts a ton of rays and draws // a sphere where they intersect for (int y = 0; y < g_yres; y += 8) { for (int x = 0; x < g_xres; x += 8) { SOverlaySphere sphere; sphere.m_Color = CColor(1, 0, 0, 1); sphere.m_Radius = 0.25f; sphere.m_Center = camera.GetWorldCoordinates(x, y, false); CVector3D origin, dir; camera.BuildCameraRay(x, y, origin, dir); for (size_t i = 0; i < m_SubmittedPatchOccluders.size(); ++i) { CPatch* occluder = m_SubmittedPatchOccluders[i]; if (CHFTracer::PatchRayIntersect(occluder, origin, dir, &sphere.m_Center)) sphere.m_Color = CColor(0, 0, 1, 1); } m_DebugSpheres.push_back(sphere); } } #endif { PROFILE("compute bounds"); for (size_t i = 0; i < m_SubmittedModelOccluders.size(); ++i) { CModel* occluder = m_SubmittedModelOccluders[i]; Occluder d; d.renderable = occluder; d.isPatch = false; d.rendered = false; ComputeScreenBounds(d, occluder->GetWorldBounds(), proj); // Skip zero-sized occluders, so we don't need to worry about EDGE_OUT // getting sorted before EDGE_IN if (d.x0 == d.x1 || d.y0 == d.y1) continue; u16 id = static_cast(occluders.size()); occluders.push_back(d); entries.push_back(EntryCreate(EDGE_IN, id, d.x0)); entries.push_back(EntryCreate(EDGE_OUT, id, d.x1)); } for (size_t i = 0; i < m_SubmittedPatchOccluders.size(); ++i) { CPatch* occluder = m_SubmittedPatchOccluders[i]; Occluder d; d.renderable = occluder; d.isPatch = true; d.rendered = false; ComputeScreenBounds(d, occluder->GetWorldBounds(), proj); // Skip zero-sized occluders if (d.x0 == d.x1 || d.y0 == d.y1) continue; u16 id = static_cast(occluders.size()); occluders.push_back(d); entries.push_back(EntryCreate(EDGE_IN, id, d.x0)); entries.push_back(EntryCreate(EDGE_OUT, id, d.x1)); } for (size_t i = 0; i < m_SubmittedModelCasters.size(); ++i) { CModel* model = m_SubmittedModelCasters[i]; CVector3D pos = model->GetTransform().GetTranslation() + posOffset; Caster d; d.model = model; d.rendered = false; ComputeScreenPos(d, pos, proj); u16 id = static_cast(casters.size()); casters.push_back(d); entries.push_back(EntryCreate(POINT, id, d.x)); } } // Make sure the u16 id didn't overflow ENSURE(occluders.size() < 65536 && casters.size() < 65536); { PROFILE("sorting"); std::sort(entries.begin(), entries.end()); } { PROFILE("sweeping"); ActiveList active; CVector3D cameraPos = camera.GetOrientation().GetTranslation(); for (size_t i = 0; i < entries.size(); ++i) { Entry e = entries[i]; int type = EntryGetType(e); u16 id = EntryGetId(e); if (type == EDGE_IN) active.Add(id); else if (type == EDGE_OUT) active.Remove(id); else { Caster& caster = casters[id]; for (size_t j = 0; j < active.m_Ids.size(); ++j) { Occluder& occluder = occluders[active.m_Ids[j]]; if (caster.y < occluder.y0 || caster.y > occluder.y1) continue; if (caster.z < occluder.z) continue; // No point checking further if both are already being rendered if (caster.rendered && occluder.rendered) continue; if (!g_DisablePreciseIntersections) { CVector3D pos = caster.model->GetTransform().GetTranslation() + posOffset; if (occluder.isPatch) { CPatch* patch = static_cast(occluder.renderable); if (!CHFTracer::PatchRayIntersect(patch, pos, cameraPos - pos, NULL)) continue; } else { float tmin, tmax; if (!occluder.renderable->GetWorldBounds().RayIntersect(pos, cameraPos - pos, tmin, tmax)) continue; } } caster.rendered = true; occluder.rendered = true; } } } } if (m_DebugEnabled) { for (size_t i = 0; i < occluders.size(); ++i) { DebugRect r; r.color = occluders[i].rendered ? CColor(1.0f, 1.0f, 0.0f, 1.0f) : CColor(0.2f, 0.2f, 0.0f, 1.0f); r.x0 = occluders[i].x0; r.y0 = occluders[i].y0; r.x1 = occluders[i].x1; r.y1 = occluders[i].y1; m_DebugRects.push_back(r); DebugBounds b; b.color = r.color; b.bounds = occluders[i].renderable->GetWorldBounds(); m_DebugBounds.push_back(b); } } for (size_t i = 0; i < occluders.size(); ++i) { if (occluders[i].rendered) { if (occluders[i].isPatch) m_VisiblePatchOccluders.push_back(static_cast(occluders[i].renderable)); else m_VisibleModelOccluders.push_back(static_cast(occluders[i].renderable)); } } for (size_t i = 0; i < casters.size(); ++i) if (casters[i].rendered) m_VisibleModelCasters.push_back(casters[i].model); } void SilhouetteRenderer::RenderSubmitOverlays(SceneCollector& collector) { for (size_t i = 0; i < m_DebugSpheres.size(); i++) collector.Submit(&m_DebugSpheres[i]); } void SilhouetteRenderer::RenderSubmitOccluders(SceneCollector& collector) { for (size_t i = 0; i < m_VisiblePatchOccluders.size(); ++i) collector.Submit(m_VisiblePatchOccluders[i]); for (size_t i = 0; i < m_VisibleModelOccluders.size(); ++i) collector.SubmitNonRecursive(m_VisibleModelOccluders[i]); } void SilhouetteRenderer::RenderSubmitCasters(SceneCollector& collector) { for (size_t i = 0; i < m_VisibleModelCasters.size(); ++i) collector.SubmitNonRecursive(m_VisibleModelCasters[i]); } void SilhouetteRenderer::RenderDebugBounds( Renderer::Backend::IDeviceCommandContext* UNUSED(deviceCommandContext)) { if (m_DebugBounds.empty()) return; for (size_t i = 0; i < m_DebugBounds.size(); ++i) g_Renderer.GetDebugRenderer().DrawBoundingBox(m_DebugBounds[i].bounds, m_DebugBounds[i].color, true); } void SilhouetteRenderer::RenderDebugOverlays( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { if (m_DebugRects.empty()) return; // TODO: use CCanvas2D for drawing rects. CMatrix3D m; m.SetIdentity(); m.Scale(1.0f, -1.f, 1.0f); m.Translate(0.0f, (float)g_yres, -1000.0f); CMatrix3D proj; proj.SetOrtho(0.f, g_MaxCoord, 0.f, g_MaxCoord, -1.f, 1000.f); m = proj * m; CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_solid); Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = shaderTech->GetGraphicsPipelineStateDesc(); deviceCommandContext->BeginPass(); pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE; pipelineStateDesc.rasterizationState.cullMode = Renderer::Backend::CullMode::NONE; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); Renderer::Backend::IShaderProgram* shader = shaderTech->GetShader(); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), proj.AsFloatArray()); const int32_t colorBindingSlot = shader->GetBindingSlot(str_color); for (const DebugRect& r : m_DebugRects) { deviceCommandContext->SetUniform( colorBindingSlot, r.color.AsFloatArray()); u16 verts[] = { r.x0, r.y0, r.x1, r.y0, r.x1, r.y1, r.x0, r.y0, r.x1, r.y1, r.x0, r.y1, }; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R16G16_SINT, 0, 0, 0); + Renderer::Backend::Format::R16G16_SINT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); - deviceCommandContext->SetVertexBufferData(0, verts); + deviceCommandContext->SetVertexBufferData( + 0, verts, std::size(verts) * sizeof(verts[0])); deviceCommandContext->Draw(0, 6); } deviceCommandContext->EndPass(); } void SilhouetteRenderer::EndFrame() { m_SubmittedPatchOccluders.clear(); m_SubmittedModelOccluders.clear(); m_SubmittedModelCasters.clear(); } Index: ps/trunk/source/renderer/SkyManager.cpp =================================================================== --- ps/trunk/source/renderer/SkyManager.cpp (revision 26900) +++ ps/trunk/source/renderer/SkyManager.cpp (revision 26901) @@ -1,346 +1,348 @@ /* 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/SkyManager.h" #include "graphics/LightEnv.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "graphics/TextureManager.h" #include "lib/bits.h" #include "lib/tex/tex.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/CStr.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/VideoMode.h" #include "renderer/backend/IDevice.h" #include "renderer/Renderer.h" #include "renderer/SceneRenderer.h" #include "renderer/RenderingOptions.h" #include SkyManager::SkyManager() : m_VertexArray(Renderer::Backend::IBuffer::Type::VERTEX, false) { CFG_GET_VAL("showsky", m_SkyVisible); } void SkyManager::LoadAndUploadSkyTexturesIfNeeded( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { if (m_SkyTextureCube) return; m_SkyTextureCube = g_Renderer.GetTextureManager().GetBlackTextureCube(); GPU_SCOPED_LABEL(deviceCommandContext, "Load Sky Textures"); static const CStrW images[NUMBER_OF_TEXTURES + 1] = { L"front", L"back", L"top", L"top", L"right", L"left" }; /*for (size_t i = 0; i < ARRAY_SIZE(m_SkyTexture); ++i) { VfsPath path = VfsPath("art/textures/skies") / m_SkySet / (Path::String(s_imageNames[i])+L".dds"); CTextureProperties textureProps(path); textureProps.SetWrap(GL_CLAMP_TO_EDGE); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); texture->Prefetch(); m_SkyTexture[i] = texture; }*/ /////////////////////////////////////////////////////////////////////////// // HACK: THE HORRIBLENESS HERE IS OVER 9000. The following code is a HUGE hack and will be removed completely // as soon as all the hardcoded GL_TEXTURE_2D references are corrected in the TextureManager/OGL/tex libs. Tex textures[NUMBER_OF_TEXTURES + 1]; for (size_t i = 0; i < NUMBER_OF_TEXTURES + 1; ++i) { VfsPath path = VfsPath("art/textures/skies") / m_SkySet / (Path::String(images[i]) + L".dds"); std::shared_ptr file; size_t fileSize; if (g_VFS->LoadFile(path, file, fileSize) != INFO::OK) { path = VfsPath("art/textures/skies") / m_SkySet / (Path::String(images[i]) + L".dds.cached.dds"); if (g_VFS->LoadFile(path, file, fileSize) != INFO::OK) { LOGERROR("Error creating sky cubemap '%s', can't load file: '%s'.", m_SkySet.ToUTF8().c_str(), path.string8().c_str()); return; } } if (textures[i].decode(file, fileSize) != INFO::OK || textures[i].transform_to((textures[i].m_Flags | TEX_BOTTOM_UP | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS)) != INFO::OK) { LOGERROR("Error creating sky cubemap '%s', can't decode file: '%s'.", m_SkySet.ToUTF8().c_str(), path.string8().c_str()); return; } if (!is_pow2(textures[i].m_Width) || !is_pow2(textures[i].m_Height)) { LOGERROR("Error creating sky cubemap '%s', cube textures should have power of 2 sizes.", m_SkySet.ToUTF8().c_str()); return; } if (textures[i].m_Width != textures[0].m_Width || textures[i].m_Height != textures[0].m_Height) { LOGERROR("Error creating sky cubemap '%s', cube textures have different sizes.", m_SkySet.ToUTF8().c_str()); return; } } std::unique_ptr skyCubeMap = g_VideoMode.GetBackendDevice()->CreateTexture("SkyCubeMap", Renderer::Backend::ITexture::Type::TEXTURE_CUBE, Renderer::Backend::Format::R8G8B8A8_UNORM, textures[0].m_Width, textures[0].m_Height, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, 1); std::vector rotated; for (size_t i = 0; i < NUMBER_OF_TEXTURES + 1; ++i) { u8* data = textures[i].get_data(); // We need to rotate the side if it's looking up or down. // TODO: maybe it should be done during texture conversion. if (i == 2 || i == 3) { rotated.resize(textures[i].m_DataSize); for (size_t y = 0; y < textures[i].m_Height; ++y) { for (size_t x = 0; x < textures[i].m_Width; ++x) { const size_t invX = y; const size_t invY = textures[i].m_Width - x - 1; rotated[(y * textures[i].m_Width + x) * 4 + 0] = data[(invY * textures[i].m_Width + invX) * 4 + 0]; rotated[(y * textures[i].m_Width + x) * 4 + 1] = data[(invY * textures[i].m_Width + invX) * 4 + 1]; rotated[(y * textures[i].m_Width + x) * 4 + 2] = data[(invY * textures[i].m_Width + invX) * 4 + 2]; rotated[(y * textures[i].m_Width + x) * 4 + 3] = data[(invY * textures[i].m_Width + invX) * 4 + 3]; } } deviceCommandContext->UploadTexture( skyCubeMap.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, &rotated[0], textures[i].m_DataSize, 0, i); } else { deviceCommandContext->UploadTexture( skyCubeMap.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, data, textures[i].m_DataSize, 0, i); } } m_SkyTextureCube = g_Renderer.GetTextureManager().WrapBackendTexture(std::move(skyCubeMap)); /////////////////////////////////////////////////////////////////////////// } Renderer::Backend::ITexture* SkyManager::GetSkyCube() { return m_SkyTextureCube->GetBackendTexture(); } void SkyManager::SetSkySet(const CStrW& newSet) { if (newSet == m_SkySet) return; m_SkyTextureCube.reset(); m_SkySet = newSet; } std::vector SkyManager::GetSkySets() const { std::vector skies; // Find all subdirectories in art/textures/skies const VfsPath path(L"art/textures/skies/"); DirectoryNames subdirectories; if (g_VFS->GetDirectoryEntries(path, 0, &subdirectories) != INFO::OK) { LOGERROR("Error opening directory '%s'", path.string8()); return std::vector(1, GetSkySet()); // just return what we currently have } for(size_t i = 0; i < subdirectories.size(); i++) skies.push_back(subdirectories[i].string()); sort(skies.begin(), skies.end()); return skies; } void SkyManager::RenderSky( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { GPU_SCOPED_LABEL(deviceCommandContext, "Render sky"); if (!m_SkyVisible) return; // Do nothing unless SetSkySet was called if (m_SkySet.empty() || !m_SkyTextureCube) return; if (m_VertexArray.GetNumberOfVertices() == 0) CreateSkyCube(); const CCamera& camera = g_Renderer.GetSceneRenderer().GetViewCamera(); CShaderTechniquePtr skytech = g_Renderer.GetShaderManager().LoadEffect(str_sky_simple); deviceCommandContext->SetGraphicsPipelineState( skytech->GetGraphicsPipelineStateDesc()); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = skytech->GetShader(); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_baseTex), m_SkyTextureCube->GetBackendTexture()); // Translate so the sky center is at the camera space origin. CMatrix3D translate; translate.SetTranslation(camera.GetOrientation().GetTranslation()); // Currently we have a hardcoded near plane in the projection matrix. CMatrix3D scale; scale.SetScaling(10.0f, 10.0f, 10.0f); // Rotate so that the "left" face, which contains the brightest part of // each skymap, is in the direction of the sun from our light // environment. CMatrix3D rotate; rotate.SetYRotation(M_PI + g_Renderer.GetSceneRenderer().GetLightEnv().GetRotation()); const CMatrix3D transform = camera.GetViewProjection() * translate * rotate * scale; deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), transform.AsFloatArray()); m_VertexArray.PrepareForRendering(); m_VertexArray.UploadIfNeeded(deviceCommandContext); const uint32_t stride = m_VertexArray.GetStride(); const uint32_t firstVertexOffset = m_VertexArray.GetOffset() * stride; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, m_AttributePosition.format, - firstVertexOffset + m_AttributePosition.offset, stride, 0); + firstVertexOffset + m_AttributePosition.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, m_AttributeUV.format, - firstVertexOffset + m_AttributeUV.offset, stride, 0); + firstVertexOffset + m_AttributeUV.offset, stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexBuffer(0, m_VertexArray.GetBuffer()); deviceCommandContext->Draw(0, m_VertexArray.GetNumberOfVertices()); deviceCommandContext->EndPass(); } void SkyManager::CreateSkyCube() { m_AttributePosition.format = Renderer::Backend::Format::R32G32B32_SFLOAT; m_VertexArray.AddAttribute(&m_AttributePosition); m_AttributeUV.format = Renderer::Backend::Format::R32G32B32_SFLOAT; m_VertexArray.AddAttribute(&m_AttributeUV); // 6 sides of cube with 6 vertices. m_VertexArray.SetNumberOfVertices(6 * 6); m_VertexArray.Layout(); VertexArrayIterator attrPosition = m_AttributePosition.GetIterator(); VertexArrayIterator attrUV = m_AttributeUV.GetIterator(); #define ADD_VERTEX(U, V, W, VX, VY, VZ) \ STMT( \ attrPosition->X = VX; \ attrPosition->Y = VY; \ attrPosition->Z = VZ; \ ++attrPosition; \ attrUV->X = U; \ attrUV->Y = V; \ attrUV->Z = W; \ ++attrUV;) // Axis -X ADD_VERTEX(+1, +1, +1, -1.0f, -1.0f, -1.0f); ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f); ADD_VERTEX(+1, -1, -1, -1.0f, +1.0f, +1.0f); ADD_VERTEX(+1, +1, +1, -1.0f, -1.0f, -1.0f); ADD_VERTEX(+1, -1, -1, -1.0f, +1.0f, +1.0f); ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f); // Axis +X ADD_VERTEX(-1, +1, -1, +1.0f, -1.0f, +1.0f); ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f); ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f); ADD_VERTEX(-1, +1, -1, +1.0f, -1.0f, +1.0f); ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f); ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f); // Axis -Y ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f); ADD_VERTEX(-1, +1, -1, +1.0f, -1.0f, +1.0f); ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f); ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f); ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f); ADD_VERTEX(+1, +1, +1, -1.0f, -1.0f, -1.0f); // Axis +Y ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f); ADD_VERTEX(+1, -1, -1, -1.0f, +1.0f, +1.0f); ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f); ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f); ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f); ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f); // Axis -Z ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f); ADD_VERTEX(+1, +1, +1, -1.0f, -1.0f, -1.0f); ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f); ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f); ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f); ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f); // Axis +Z ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f); ADD_VERTEX(-1, +1, -1, +1.0f, -1.0f, +1.0f); ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f); ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f); ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f); ADD_VERTEX(+1, -1, -1, -1.0f, +1.0f, +1.0f); #undef ADD_VERTEX m_VertexArray.Upload(); m_VertexArray.FreeBackingStore(); } Index: ps/trunk/source/renderer/TerrainOverlay.cpp =================================================================== --- ps/trunk/source/renderer/TerrainOverlay.cpp (revision 26900) +++ ps/trunk/source/renderer/TerrainOverlay.cpp (revision 26901) @@ -1,383 +1,387 @@ /* 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 "TerrainOverlay.h" #include "graphics/Color.h" #include "graphics/ShaderManager.h" #include "graphics/ShaderProgram.h" #include "graphics/Terrain.h" #include "lib/bits.h" #include "maths/MathUtil.h" #include "ps/CStrInternStatic.h" #include "ps/Game.h" #include "ps/Profile.h" #include "ps/World.h" #include "renderer/backend/IDevice.h" #include "renderer/backend/IDeviceCommandContext.h" #include "renderer/Renderer.h" #include "renderer/SceneRenderer.h" #include "renderer/TerrainRenderer.h" #include "simulation2/system/SimContext.h" #include // Global overlay list management: static std::vector > g_TerrainOverlayList; ITerrainOverlay::ITerrainOverlay(int priority) { // Add to global list of overlays g_TerrainOverlayList.emplace_back(this, priority); // Sort by overlays by priority. Do stable sort so that adding/removing // overlays doesn't randomly disturb all the existing ones (which would // be noticeable if they have the same priority and overlap). std::stable_sort(g_TerrainOverlayList.begin(), g_TerrainOverlayList.end(), [](const std::pair& a, const std::pair& b) { return a.second < b.second; }); } ITerrainOverlay::~ITerrainOverlay() { std::vector >::iterator newEnd = std::remove_if(g_TerrainOverlayList.begin(), g_TerrainOverlayList.end(), [this](const std::pair& a) { return a.first == this; }); g_TerrainOverlayList.erase(newEnd, g_TerrainOverlayList.end()); } void ITerrainOverlay::RenderOverlaysBeforeWater( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { if (g_TerrainOverlayList.empty()) return; PROFILE3_GPU("terrain overlays (before)"); GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain overlays before water"); for (size_t i = 0; i < g_TerrainOverlayList.size(); ++i) g_TerrainOverlayList[i].first->RenderBeforeWater(deviceCommandContext); } void ITerrainOverlay::RenderOverlaysAfterWater( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, int cullGroup) { if (g_TerrainOverlayList.empty()) return; PROFILE3_GPU("terrain overlays (after)"); GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain overlays after water"); for (size_t i = 0; i < g_TerrainOverlayList.size(); ++i) g_TerrainOverlayList[i].first->RenderAfterWater(deviceCommandContext, cullGroup); } ////////////////////////////////////////////////////////////////////////// TerrainOverlay::TerrainOverlay(const CSimContext& simContext, int priority /* = 100 */) : ITerrainOverlay(priority), m_Terrain(&simContext.GetTerrain()) { } void TerrainOverlay::StartRender() { } void TerrainOverlay::EndRender() { } void TerrainOverlay::GetTileExtents( ssize_t& min_i_inclusive, ssize_t& min_j_inclusive, ssize_t& max_i_inclusive, ssize_t& max_j_inclusive) { // Default to whole map min_i_inclusive = min_j_inclusive = 0; max_i_inclusive = max_j_inclusive = m_Terrain->GetTilesPerSide()-1; } void TerrainOverlay::RenderBeforeWater( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { if (!m_Terrain) return; // should never happen, but let's play it safe StartRender(); ssize_t min_i, min_j, max_i, max_j; GetTileExtents(min_i, min_j, max_i, max_j); // Clamp the min to 0, but the max to -1 - so tile -1 can never be rendered, // but if unclamped_max<0 then no tiles at all will be rendered. And the same // for the upper limit. min_i = Clamp(min_i, 0, m_Terrain->GetTilesPerSide()); min_j = Clamp(min_j, 0, m_Terrain->GetTilesPerSide()); max_i = Clamp(max_i, -1, m_Terrain->GetTilesPerSide()-1); max_j = Clamp(max_j, -1, m_Terrain->GetTilesPerSide()-1); for (m_j = min_j; m_j <= max_j; ++m_j) for (m_i = min_i; m_i <= max_i; ++m_i) ProcessTile(deviceCommandContext, m_i, m_j); EndRender(); } void TerrainOverlay::RenderTile( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CColor& color, bool drawHidden) { RenderTile(deviceCommandContext, color, drawHidden, m_i, m_j); } void TerrainOverlay::RenderTile( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CColor& color, bool drawHidden, ssize_t i, ssize_t j) { // TODO: unnecessary computation calls has been removed but we should use // a vertex buffer or a vertex shader with a texture. // Not sure if it's possible on old OpenGL. CVector3D pos[2][2]; for (int di = 0; di < 2; ++di) for (int dj = 0; dj < 2; ++dj) m_Terrain->CalcPosition(i + di, j + dj, pos[di][dj]); std::vector vertices; #define ADD(position) \ vertices.emplace_back((position).X); \ vertices.emplace_back((position).Y); \ vertices.emplace_back((position).Z); if (m_Terrain->GetTriangulationDir(i, j)) { ADD(pos[0][0]); ADD(pos[1][0]); ADD(pos[0][1]); ADD(pos[1][0]); ADD(pos[1][1]); ADD(pos[0][1]); } else { ADD(pos[0][0]); ADD(pos[1][0]); ADD(pos[1][1]); ADD(pos[1][1]); ADD(pos[0][1]); ADD(pos[0][0]); } #undef ADD CShaderTechniquePtr overlayTech = g_Renderer.GetShaderManager().LoadEffect(str_debug_line); Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = overlayTech->GetGraphicsPipelineStateDesc(); pipelineStateDesc.depthStencilState.depthTestEnabled = !drawHidden; pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; pipelineStateDesc.rasterizationState.cullMode = drawHidden ? Renderer::Backend::CullMode::NONE : Renderer::Backend::CullMode::BACK; // To ensure that outlines are drawn on top of the terrain correctly (and // don't Z-fight and flicker nastily), use detph bias to pull them towards // the camera. pipelineStateDesc.rasterizationState.depthBiasEnabled = true; pipelineStateDesc.rasterizationState.depthBiasConstantFactor = -1.0f; pipelineStateDesc.rasterizationState.depthBiasSlopeFactor = -1.0f; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* overlayShader = overlayTech->GetShader(); const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( overlayShader->GetBindingSlot(str_transform), transform.AsFloatArray()); deviceCommandContext->SetUniform( overlayShader->GetBindingSlot(str_color), color.AsFloatArray()); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, 0); + Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); - deviceCommandContext->SetVertexBufferData(0, vertices.data()); + deviceCommandContext->SetVertexBufferData( + 0, vertices.data(), vertices.size() * sizeof(vertices[0])); deviceCommandContext->Draw(0, vertices.size() / 3); deviceCommandContext->EndPass(); } void TerrainOverlay::RenderTileOutline( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CColor& color, bool drawHidden) { RenderTileOutline(deviceCommandContext, color, drawHidden, m_i, m_j); } void TerrainOverlay::RenderTileOutline( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CColor& color, bool drawHidden, ssize_t i, ssize_t j) { std::vector vertices; #define ADD(i, j) \ m_Terrain->CalcPosition(i, j, position); \ vertices.emplace_back(position.X); \ vertices.emplace_back(position.Y); \ vertices.emplace_back(position.Z); CVector3D position; ADD(i, j); ADD(i + 1, j); ADD(i + 1, j + 1); ADD(i, j); ADD(i + 1, j + 1); ADD(i, j + 1); #undef ADD CShaderTechniquePtr overlayTech = g_Renderer.GetShaderManager().LoadEffect(str_debug_line); Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = overlayTech->GetGraphicsPipelineStateDesc(); pipelineStateDesc.depthStencilState.depthTestEnabled = !drawHidden; pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; pipelineStateDesc.rasterizationState.cullMode = drawHidden ? Renderer::Backend::CullMode::NONE : Renderer::Backend::CullMode::BACK; pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* overlayShader = overlayTech->GetShader(); const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( overlayShader->GetBindingSlot(str_transform), transform.AsFloatArray()); deviceCommandContext->SetUniform( overlayShader->GetBindingSlot(str_color), color.AsFloatArray()); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, 0); + Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); - deviceCommandContext->SetVertexBufferData(0, vertices.data()); + deviceCommandContext->SetVertexBufferData( + 0, vertices.data(), vertices.size() * sizeof(vertices[0])); deviceCommandContext->Draw(0, vertices.size() / 3); deviceCommandContext->EndPass(); } ////////////////////////////////////////////////////////////////////////// TerrainTextureOverlay::TerrainTextureOverlay(float texelsPerTile, int priority) : ITerrainOverlay(priority), m_TexelsPerTile(texelsPerTile) { } TerrainTextureOverlay::~TerrainTextureOverlay() = default; void TerrainTextureOverlay::RenderAfterWater( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, int cullGroup) { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); ssize_t w = (ssize_t)(terrain->GetTilesPerSide() * m_TexelsPerTile); ssize_t h = (ssize_t)(terrain->GetTilesPerSide() * m_TexelsPerTile); const uint32_t requiredWidth = round_up_to_pow2(w); const uint32_t requiredHeight = round_up_to_pow2(h); // Recreate the texture with new size if necessary if (!m_Texture || m_Texture->GetWidth() != requiredWidth || m_Texture->GetHeight() != requiredHeight) { m_Texture = deviceCommandContext->GetDevice()->CreateTexture2D("TerrainOverlayTexture", Renderer::Backend::Format::R8G8B8A8_UNORM, requiredWidth, requiredHeight, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::NEAREST, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)); } u8* data = (u8*)calloc(w * h, 4); BuildTextureRGBA(data, w, h); deviceCommandContext->UploadTextureRegion( m_Texture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, data, w * h * 4, 0, 0, w, h); free(data); CMatrix3D matrix; matrix.SetZero(); matrix._11 = m_TexelsPerTile / (m_Texture->GetWidth() * TERRAIN_TILE_SIZE); matrix._23 = m_TexelsPerTile / (m_Texture->GetHeight() * TERRAIN_TILE_SIZE); matrix._44 = 1; g_Renderer.GetSceneRenderer().GetTerrainRenderer().RenderTerrainOverlayTexture( deviceCommandContext, cullGroup, matrix, m_Texture.get()); } SColor4ub TerrainTextureOverlay::GetColor(size_t idx, u8 alpha) const { static u8 colors[][3] = { { 255, 0, 0 }, { 0, 255, 0 }, { 0, 0, 255 }, { 255, 255, 0 }, { 255, 0, 255 }, { 0, 255, 255 }, { 255, 255, 255 }, { 127, 0, 0 }, { 0, 127, 0 }, { 0, 0, 127 }, { 127, 127, 0 }, { 127, 0, 127 }, { 0, 127, 127 }, { 127, 127, 127}, { 255, 127, 0 }, { 127, 255, 0 }, { 255, 0, 127 }, { 127, 0, 255}, { 0, 255, 127 }, { 0, 127, 255}, { 255, 127, 127}, { 127, 255, 127}, { 127, 127, 255}, { 127, 255, 255 }, { 255, 127, 255 }, { 255, 255, 127 }, }; size_t c = idx % ARRAY_SIZE(colors); return SColor4ub(colors[c][0], colors[c][1], colors[c][2], alpha); } Index: ps/trunk/source/renderer/TerrainRenderer.cpp =================================================================== --- ps/trunk/source/renderer/TerrainRenderer.cpp (revision 26900) +++ ps/trunk/source/renderer/TerrainRenderer.cpp (revision 26901) @@ -1,723 +1,726 @@ /* 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/TerrainRenderer.h" #include "graphics/Camera.h" #include "graphics/Canvas2D.h" #include "graphics/Decal.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/LOSTexture.h" #include "graphics/Patch.h" #include "graphics/Model.h" #include "graphics/ShaderManager.h" #include "graphics/TerritoryTexture.h" #include "graphics/TextRenderer.h" #include "graphics/TextureManager.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/Profile.h" #include "ps/World.h" #include "renderer/backend/IDevice.h" #include "renderer/DecalRData.h" #include "renderer/PatchRData.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/SceneRenderer.h" #include "renderer/ShadowMap.h" #include "renderer/SkyManager.h" #include "renderer/VertexArray.h" #include "renderer/WaterManager.h" /** * TerrainRenderer keeps track of which phase it is in, to detect * when Submit, PrepareForRendering etc. are called in the wrong order. */ enum Phase { Phase_Submit, Phase_Render }; /** * Struct TerrainRendererInternals: Internal variables used by the TerrainRenderer class. */ struct TerrainRendererInternals { /// Which phase (submitting or rendering patches) are we in right now? Phase phase; /// Patches that were submitted for this frame std::vector visiblePatches[CSceneRenderer::CULL_MAX]; /// Decals that were submitted for this frame std::vector visibleDecals[CSceneRenderer::CULL_MAX]; /// Fancy water shader CShaderTechniquePtr fancyWaterTech; CSimulation2* simulation; }; /////////////////////////////////////////////////////////////////// // Construction/Destruction TerrainRenderer::TerrainRenderer() { m = new TerrainRendererInternals(); m->phase = Phase_Submit; } TerrainRenderer::~TerrainRenderer() { delete m; } void TerrainRenderer::SetSimulation(CSimulation2* simulation) { m->simulation = simulation; } /////////////////////////////////////////////////////////////////// // Submit a patch for rendering void TerrainRenderer::Submit(int cullGroup, CPatch* patch) { ENSURE(m->phase == Phase_Submit); CPatchRData* data = (CPatchRData*)patch->GetRenderData(); if (data == 0) { // no renderdata for patch, create it now data = new CPatchRData(patch, m->simulation); patch->SetRenderData(data); } data->Update(m->simulation); m->visiblePatches[cullGroup].push_back(data); } /////////////////////////////////////////////////////////////////// // Submit a decal for rendering void TerrainRenderer::Submit(int cullGroup, CModelDecal* decal) { ENSURE(m->phase == Phase_Submit); CDecalRData* data = (CDecalRData*)decal->GetRenderData(); if (data == 0) { // no renderdata for decal, create it now data = new CDecalRData(decal, m->simulation); decal->SetRenderData(data); } data->Update(m->simulation); m->visibleDecals[cullGroup].push_back(data); } /////////////////////////////////////////////////////////////////// // Prepare for rendering void TerrainRenderer::PrepareForRendering() { ENSURE(m->phase == Phase_Submit); m->phase = Phase_Render; } /////////////////////////////////////////////////////////////////// // Clear submissions lists void TerrainRenderer::EndFrame() { ENSURE(m->phase == Phase_Render || m->phase == Phase_Submit); for (int i = 0; i < CSceneRenderer::CULL_MAX; ++i) { m->visiblePatches[i].clear(); m->visibleDecals[i].clear(); } m->phase = Phase_Submit; } void TerrainRenderer::RenderTerrainOverlayTexture( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, int cullGroup, CMatrix3D& textureMatrix, Renderer::Backend::ITexture* texture) { ENSURE(m->phase == Phase_Render); std::vector& visiblePatches = m->visiblePatches[cullGroup]; CShaderTechniquePtr debugOverlayTech = g_Renderer.GetShaderManager().LoadEffect(str_debug_overlay); deviceCommandContext->SetGraphicsPipelineState( debugOverlayTech->GetGraphicsPipelineStateDesc()); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* debugOverlayShader = debugOverlayTech->GetShader(); deviceCommandContext->SetTexture( debugOverlayShader->GetBindingSlot(str_baseTex), texture); const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( debugOverlayShader->GetBindingSlot(str_transform), transform.AsFloatArray()); deviceCommandContext->SetUniform( debugOverlayShader->GetBindingSlot(str_textureTransform), textureMatrix.AsFloatArray()); CPatchRData::RenderStreams(deviceCommandContext, visiblePatches, true); // To make the overlay visible over water, render an additional map-sized // water-height patch. CBoundingBoxAligned waterBounds; for (CPatchRData* data : visiblePatches) waterBounds += data->GetWaterBounds(); if (!waterBounds.IsEmpty()) { // Add a delta to avoid z-fighting. const float height = g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight + 0.05f; const float waterPos[] = { waterBounds[0].X, height, waterBounds[0].Z, waterBounds[1].X, height, waterBounds[0].Z, waterBounds[1].X, height, waterBounds[1].Z, waterBounds[0].X, height, waterBounds[0].Z, waterBounds[1].X, height, waterBounds[1].Z, waterBounds[0].X, height, waterBounds[1].Z }; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, - Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, 0); + Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, - Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, 0); + Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); - deviceCommandContext->SetVertexBufferData(0, waterPos); + deviceCommandContext->SetVertexBufferData( + 0, waterPos, std::size(waterPos) * sizeof(waterPos[0])); deviceCommandContext->Draw(0, 6); } deviceCommandContext->EndPass(); } /////////////////////////////////////////////////////////////////// /** * Set up all the uniforms for a shader pass. */ void TerrainRenderer::PrepareShader( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IShaderProgram* shader, ShadowMap* shadow) { CSceneRenderer& sceneRenderer = g_Renderer.GetSceneRenderer(); const CMatrix3D transform = sceneRenderer.GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), transform.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_cameraPos), sceneRenderer.GetViewCamera().GetOrientation().GetTranslation().AsFloatArray()); const CLightEnv& lightEnv = sceneRenderer.GetLightEnv(); if (shadow) shadow->BindTo(deviceCommandContext, shader); CLOSTexture& los = sceneRenderer.GetScene().GetLOSTexture(); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_losTex), los.GetTextureSmooth()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_losTransform), los.GetTextureMatrix()[0], los.GetTextureMatrix()[12]); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_ambient), lightEnv.m_AmbientColor.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_sunColor), lightEnv.m_SunColor.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_sunDir), lightEnv.GetSunDir().AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_fogColor), lightEnv.m_FogColor.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_fogParams), lightEnv.m_FogFactor, lightEnv.m_FogMax); } void TerrainRenderer::RenderTerrainShader( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CShaderDefines& context, int cullGroup, ShadowMap* shadow) { ENSURE(m->phase == Phase_Render); std::vector& visiblePatches = m->visiblePatches[cullGroup]; std::vector& visibleDecals = m->visibleDecals[cullGroup]; if (visiblePatches.empty() && visibleDecals.empty()) return; // render the solid black sides of the map first CShaderTechniquePtr techSolid = g_Renderer.GetShaderManager().LoadEffect(str_solid); Renderer::Backend::GraphicsPipelineStateDesc solidPipelineStateDesc = techSolid->GetGraphicsPipelineStateDesc(); solidPipelineStateDesc.rasterizationState.cullMode = Renderer::Backend::CullMode::NONE; deviceCommandContext->SetGraphicsPipelineState(solidPipelineStateDesc); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shaderSolid = techSolid->GetShader(); const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( shaderSolid->GetBindingSlot(str_transform), transform.AsFloatArray()); deviceCommandContext->SetUniform( shaderSolid->GetBindingSlot(str_color), 0.0f, 0.0f, 0.0f, 1.0f); CPatchRData::RenderSides(deviceCommandContext, visiblePatches); deviceCommandContext->EndPass(); CPatchRData::RenderBases(deviceCommandContext, visiblePatches, context, shadow); // render blend passes for each patch CPatchRData::RenderBlends(deviceCommandContext, visiblePatches, context, shadow); CDecalRData::RenderDecals(deviceCommandContext, visibleDecals, context, shadow); } /////////////////////////////////////////////////////////////////// // Render un-textured patches as polygons void TerrainRenderer::RenderPatches( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, int cullGroup, const CShaderDefines& defines, const CColor& color) { ENSURE(m->phase == Phase_Render); std::vector& visiblePatches = m->visiblePatches[cullGroup]; if (visiblePatches.empty()) return; GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain patches"); CShaderTechniquePtr solidTech = g_Renderer.GetShaderManager().LoadEffect(str_terrain_solid, defines); deviceCommandContext->SetGraphicsPipelineState( solidTech->GetGraphicsPipelineStateDesc()); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* solidShader = solidTech->GetShader(); const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( solidShader->GetBindingSlot(str_transform), transform.AsFloatArray()); deviceCommandContext->SetUniform( solidShader->GetBindingSlot(str_color), color.AsFloatArray()); CPatchRData::RenderStreams(deviceCommandContext, visiblePatches, false); deviceCommandContext->EndPass(); } /////////////////////////////////////////////////////////////////// // Render outlines of submitted patches as lines void TerrainRenderer::RenderOutlines( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, int cullGroup) { ENSURE(m->phase == Phase_Render); std::vector& visiblePatches = m->visiblePatches[cullGroup]; if (visiblePatches.empty()) return; GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain outlines"); for (size_t i = 0; i < visiblePatches.size(); ++i) visiblePatches[i]->RenderOutline(); } /////////////////////////////////////////////////////////////////// // Scissor rectangle of water patches CBoundingBoxAligned TerrainRenderer::ScissorWater(int cullGroup, const CCamera& camera) { CBoundingBoxAligned scissor; for (const CPatchRData* data : m->visiblePatches[cullGroup]) { const CBoundingBoxAligned& waterBounds = data->GetWaterBounds(); if (waterBounds.IsEmpty()) continue; const CBoundingBoxAligned waterBoundsInViewPort = camera.GetBoundsInViewPort(waterBounds); if (!waterBoundsInViewPort.IsEmpty()) scissor += waterBoundsInViewPort; } return CBoundingBoxAligned( CVector3D(Clamp(scissor[0].X, -1.0f, 1.0f), Clamp(scissor[0].Y, -1.0f, 1.0f), -1.0f), CVector3D(Clamp(scissor[1].X, -1.0f, 1.0f), Clamp(scissor[1].Y, -1.0f, 1.0f), 1.0f)); } // Render fancy water bool TerrainRenderer::RenderFancyWater( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CShaderDefines& context, int cullGroup, ShadowMap* shadow) { PROFILE3_GPU("fancy water"); GPU_SCOPED_LABEL(deviceCommandContext, "Render fancy water"); CSceneRenderer& sceneRenderer = g_Renderer.GetSceneRenderer(); WaterManager& waterManager = sceneRenderer.GetWaterManager(); CShaderDefines defines = context; // If we're using fancy water, make sure its shader is loaded if (!m->fancyWaterTech || waterManager.m_NeedsReloading) { if (waterManager.m_WaterRealDepth) defines.Add(str_USE_REAL_DEPTH, str_1); if (waterManager.m_WaterFancyEffects) defines.Add(str_USE_FANCY_EFFECTS, str_1); if (waterManager.m_WaterRefraction) defines.Add(str_USE_REFRACTION, str_1); if (waterManager.m_WaterReflection) defines.Add(str_USE_REFLECTION, str_1); m->fancyWaterTech = g_Renderer.GetShaderManager().LoadEffect(str_water_high, defines); if (!m->fancyWaterTech) { LOGERROR("Failed to load water shader. Falling back to a simple water.\n"); waterManager.m_RenderWater = false; return false; } waterManager.m_NeedsReloading = false; } CLOSTexture& losTexture = sceneRenderer.GetScene().GetLOSTexture(); // Calculating the advanced informations about Foam and all if the quality calls for it. /*if (WaterMgr->m_NeedInfoUpdate && (WaterMgr->m_WaterFoam || WaterMgr->m_WaterCoastalWaves)) { WaterMgr->m_NeedInfoUpdate = false; WaterMgr->CreateSuperfancyInfo(); }*/ const double time = waterManager.m_WaterTexTimer; const float repeatPeriod = waterManager.m_RepeatPeriod; deviceCommandContext->SetGraphicsPipelineState( m->fancyWaterTech->GetGraphicsPipelineStateDesc()); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* fancyWaterShader = m->fancyWaterTech->GetShader(); const CCamera& camera = g_Renderer.GetSceneRenderer().GetViewCamera(); const double period = 8.0; // TODO: move uploading to a prepare function during loading. const CTexturePtr& currentNormalTexture = waterManager.m_NormalMap[waterManager.GetCurrentTextureIndex(period)]; const CTexturePtr& nextNormalTexture = waterManager.m_NormalMap[waterManager.GetNextTextureIndex(period)]; currentNormalTexture->UploadBackendTextureIfNeeded(deviceCommandContext); nextNormalTexture->UploadBackendTextureIfNeeded(deviceCommandContext); deviceCommandContext->SetTexture( fancyWaterShader->GetBindingSlot(str_normalMap), currentNormalTexture->GetBackendTexture()); deviceCommandContext->SetTexture( fancyWaterShader->GetBindingSlot(str_normalMap2), nextNormalTexture->GetBackendTexture()); if (waterManager.m_WaterFancyEffects) { deviceCommandContext->SetTexture( fancyWaterShader->GetBindingSlot(str_waterEffectsTex), waterManager.m_FancyTexture.get()); } if (waterManager.m_WaterRefraction && waterManager.m_WaterRealDepth) { deviceCommandContext->SetTexture( fancyWaterShader->GetBindingSlot(str_depthTex), waterManager.m_RefrFboDepthTexture.get()); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_projInvTransform), waterManager.m_RefractionProjInvMatrix.AsFloatArray()); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_viewInvTransform), waterManager.m_RefractionViewInvMatrix.AsFloatArray()); } if (waterManager.m_WaterRefraction) { deviceCommandContext->SetTexture( fancyWaterShader->GetBindingSlot(str_refractionMap), waterManager.m_RefractionTexture.get()); } if (waterManager.m_WaterReflection) { deviceCommandContext->SetTexture( fancyWaterShader->GetBindingSlot(str_reflectionMap), waterManager.m_ReflectionTexture.get()); } deviceCommandContext->SetTexture( fancyWaterShader->GetBindingSlot(str_losTex), losTexture.GetTextureSmooth()); const CLightEnv& lightEnv = sceneRenderer.GetLightEnv(); const CMatrix3D transform = sceneRenderer.GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_transform), transform.AsFloatArray()); deviceCommandContext->SetTexture( fancyWaterShader->GetBindingSlot(str_skyCube), sceneRenderer.GetSkyManager().GetSkyCube()); // TODO: check that this rotates in the right direction. CMatrix3D skyBoxRotation; skyBoxRotation.SetIdentity(); skyBoxRotation.RotateY(M_PI + lightEnv.GetRotation()); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_skyBoxRot), skyBoxRotation.AsFloatArray()); if (waterManager.m_WaterRefraction) { deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_refractionMatrix), waterManager.m_RefractionMatrix.AsFloatArray()); } if (waterManager.m_WaterReflection) { deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_reflectionMatrix), waterManager.m_ReflectionMatrix.AsFloatArray()); } deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_ambient), lightEnv.m_AmbientColor.AsFloatArray()); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_sunDir), lightEnv.GetSunDir().AsFloatArray()); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_sunColor), lightEnv.m_SunColor.AsFloatArray()); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_color), waterManager.m_WaterColor.AsFloatArray()); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_tint), waterManager.m_WaterTint.AsFloatArray()); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_waviness), waterManager.m_Waviness); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_murkiness), waterManager.m_Murkiness); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_windAngle), waterManager.m_WindAngle); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_repeatScale), 1.0f / repeatPeriod); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_losTransform), losTexture.GetTextureMatrix()[0], losTexture.GetTextureMatrix()[12]); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_cameraPos), camera.GetOrientation().GetTranslation().AsFloatArray()); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_fogColor), lightEnv.m_FogColor.AsFloatArray()); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_fogParams), lightEnv.m_FogFactor, lightEnv.m_FogMax); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_time), static_cast(time)); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_screenSize), static_cast(g_Renderer.GetWidth()), static_cast(g_Renderer.GetHeight())); if (waterManager.m_WaterType == L"clap") { deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_waveParams1), 30.0f, 1.5f, 20.0f, 0.03f); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_waveParams2), 0.5f, 0.0f, 0.0f, 0.0f); } else if (waterManager.m_WaterType == L"lake") { deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_waveParams1), 8.5f, 1.5f, 15.0f, 0.03f); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_waveParams2), 0.2f, 0.0f, 0.0f, 0.07f); } else { deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_waveParams1), 15.0f, 0.8f, 10.0f, 0.1f); deviceCommandContext->SetUniform( fancyWaterShader->GetBindingSlot(str_waveParams2), 0.3f, 0.0f, 0.1f, 0.3f); } if (shadow) shadow->BindTo(deviceCommandContext, fancyWaterShader); for (CPatchRData* data : m->visiblePatches[cullGroup]) { data->RenderWaterSurface(deviceCommandContext, true); if (waterManager.m_WaterFancyEffects) data->RenderWaterShore(deviceCommandContext); } deviceCommandContext->EndPass(); return true; } void TerrainRenderer::RenderSimpleWater( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, int cullGroup) { PROFILE3_GPU("simple water"); GPU_SCOPED_LABEL(deviceCommandContext, "Render Simple Water"); const WaterManager& waterManager = g_Renderer.GetSceneRenderer().GetWaterManager(); CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture(); const double time = waterManager.m_WaterTexTimer; CShaderDefines context; if (g_Renderer.GetSceneRenderer().GetWaterRenderMode() == WIREFRAME) context.Add(str_MODE_WIREFRAME, str_1); CShaderTechniquePtr waterSimpleTech = g_Renderer.GetShaderManager().LoadEffect(str_water_simple, context); deviceCommandContext->SetGraphicsPipelineState( waterSimpleTech->GetGraphicsPipelineStateDesc()); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* waterSimpleShader = waterSimpleTech->GetShader(); const CTexturePtr& waterTexture = waterManager.m_WaterTexture[waterManager.GetCurrentTextureIndex(1.6)]; waterTexture->UploadBackendTextureIfNeeded(deviceCommandContext); deviceCommandContext->SetTexture( waterSimpleShader->GetBindingSlot(str_baseTex), waterTexture->GetBackendTexture()); deviceCommandContext->SetTexture( waterSimpleShader->GetBindingSlot(str_losTex), losTexture.GetTextureSmooth()); const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( waterSimpleShader->GetBindingSlot(str_transform), transform.AsFloatArray()); deviceCommandContext->SetUniform( waterSimpleShader->GetBindingSlot(str_losTransform), losTexture.GetTextureMatrix()[0], losTexture.GetTextureMatrix()[12]); deviceCommandContext->SetUniform( waterSimpleShader->GetBindingSlot(str_time), static_cast(time)); deviceCommandContext->SetUniform( waterSimpleShader->GetBindingSlot(str_color), waterManager.m_WaterColor.AsFloatArray()); std::vector& visiblePatches = m->visiblePatches[cullGroup]; for (size_t i = 0; i < visiblePatches.size(); ++i) { CPatchRData* data = visiblePatches[i]; data->RenderWaterSurface(deviceCommandContext, false); } deviceCommandContext->EndPass(); } /////////////////////////////////////////////////////////////////// // Render water that is part of the terrain void TerrainRenderer::RenderWater( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CShaderDefines& context, int cullGroup, ShadowMap* shadow) { const WaterManager& waterManager = g_Renderer.GetSceneRenderer().GetWaterManager(); if (!waterManager.WillRenderFancyWater()) RenderSimpleWater(deviceCommandContext, cullGroup); else RenderFancyWater(deviceCommandContext, context, cullGroup, shadow); } void TerrainRenderer::RenderWaterFoamOccluders( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, int cullGroup) { CSceneRenderer& sceneRenderer = g_Renderer.GetSceneRenderer(); const WaterManager& waterManager = sceneRenderer.GetWaterManager(); if (!waterManager.WillRenderFancyWater()) return; GPU_SCOPED_LABEL(deviceCommandContext, "Render water foam occluders"); // Render normals and foam to a framebuffer if we're using fancy effects. deviceCommandContext->SetFramebuffer(waterManager.m_FancyEffectsFramebuffer.get()); // Overwrite waves that would be behind the ground. CShaderTechniquePtr dummyTech = g_Renderer.GetShaderManager().LoadEffect(str_solid); Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = dummyTech->GetGraphicsPipelineStateDesc(); pipelineStateDesc.depthStencilState.depthTestEnabled = true; pipelineStateDesc.rasterizationState.cullMode = Renderer::Backend::CullMode::NONE; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* dummyShader = dummyTech->GetShader(); const CMatrix3D transform = sceneRenderer.GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( dummyShader->GetBindingSlot(str_transform), transform.AsFloatArray()); deviceCommandContext->SetUniform( dummyShader->GetBindingSlot(str_color), 0.0f, 0.0f, 0.0f, 0.0f); for (CPatchRData* data : m->visiblePatches[cullGroup]) data->RenderWaterShore(deviceCommandContext); deviceCommandContext->EndPass(); deviceCommandContext->SetFramebuffer( deviceCommandContext->GetDevice()->GetCurrentBackbuffer()); } void TerrainRenderer::RenderPriorities(CCanvas2D& canvas, int cullGroup) { PROFILE("priorities"); ENSURE(m->phase == Phase_Render); CTextRenderer textRenderer; textRenderer.SetCurrentFont(CStrIntern("mono-stroke-10")); textRenderer.SetCurrentColor(CColor(1.0f, 1.0f, 0.0f, 1.0f)); std::vector& visiblePatches = m->visiblePatches[cullGroup]; for (size_t i = 0; i < visiblePatches.size(); ++i) visiblePatches[i]->RenderPriorities(textRenderer); canvas.DrawText(textRenderer); } Index: ps/trunk/source/renderer/TexturedLineRData.cpp =================================================================== --- ps/trunk/source/renderer/TexturedLineRData.cpp (revision 26900) +++ ps/trunk/source/renderer/TexturedLineRData.cpp (revision 26901) @@ -1,465 +1,468 @@ /* 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 "TexturedLineRData.h" #include "graphics/ShaderProgram.h" #include "graphics/Terrain.h" #include "maths/Frustum.h" #include "maths/MathUtil.h" #include "maths/Quaternion.h" #include "ps/CStrInternStatic.h" #include "renderer/OverlayRenderer.h" #include "renderer/Renderer.h" #include "simulation2/Simulation2.h" #include "simulation2/system/SimContext.h" #include "simulation2/components/ICmpWaterManager.h" /* Note: this implementation uses g_VBMan directly rather than access it through the nicer VertexArray interface, * because it allows you to work with variable amounts of vertices and indices more easily. New code should prefer * to use VertexArray where possible, though. */ void CTexturedLineRData::Render( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const SOverlayTexturedLine& line, Renderer::Backend::IShaderProgram* shader) { if (!m_VB || !m_VBIndices) return; // might have failed to allocate // -- render main line quad strip ---------------------- line.m_TextureBase->UploadBackendTextureIfNeeded(deviceCommandContext); line.m_TextureMask->UploadBackendTextureIfNeeded(deviceCommandContext); m_VB->m_Owner->UploadIfNeeded(deviceCommandContext); m_VBIndices->m_Owner->UploadIfNeeded(deviceCommandContext); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_baseTex), line.m_TextureBase->GetBackendTexture()); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_maskTex), line.m_TextureMask->GetBackendTexture()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_objectColor), line.m_Color.AsFloatArray()); const uint32_t stride = sizeof(CTexturedLineRData::SVertex); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, Renderer::Backend::Format::R32G32B32_SFLOAT, - offsetof(CTexturedLineRData::SVertex, m_Position), stride, 0); + offsetof(CTexturedLineRData::SVertex, m_Position), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, Renderer::Backend::Format::R32G32_SFLOAT, - offsetof(CTexturedLineRData::SVertex, m_UVs), stride, 0); + offsetof(CTexturedLineRData::SVertex, m_UVs), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV1, Renderer::Backend::Format::R32G32_SFLOAT, - offsetof(CTexturedLineRData::SVertex, m_UVs), stride, 0); + offsetof(CTexturedLineRData::SVertex, m_UVs), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexBuffer(0, m_VB->m_Owner->GetBuffer()); deviceCommandContext->SetIndexBuffer(m_VBIndices->m_Owner->GetBuffer()); deviceCommandContext->DrawIndexed(m_VBIndices->m_Index, m_VBIndices->m_Count, 0); g_Renderer.GetStats().m_DrawCalls++; g_Renderer.GetStats().m_OverlayTris += m_VBIndices->m_Count/3; } void CTexturedLineRData::Update(const SOverlayTexturedLine& line) { m_VBIndices.Reset(); m_VB.Reset(); if (!line.m_SimContext) { debug_warn(L"[TexturedLineRData] No SimContext set for textured overlay line, cannot render (no terrain data)"); return; } float v = 0.f; std::vector vertices; std::vector indices; const size_t n = line.m_Coords.size(); // number of line points bool closed = line.m_Closed; ENSURE(n >= 2); // minimum needed to avoid errors (also minimum value to make sense, can't draw a line between 1 point) // In each iteration, p1 is the position of vertex i, p0 is i-1, p2 is i+1. // To avoid slightly expensive terrain computations we cycle these around and // recompute p2 at the end of each iteration. CVector3D p0; CVector3D p1(line.m_Coords[0].X, 0, line.m_Coords[0].Y); CVector3D p2(line.m_Coords[1].X, 0, line.m_Coords[1].Y); if (closed) // grab the ending point so as to close the loop p0 = CVector3D(line.m_Coords[n - 1].X, 0, line.m_Coords[n - 1].Y); else // we don't want to loop around and use the direction towards the other end of the line, so create an artificial p0 that // extends the p2 -> p1 direction, and use that point instead p0 = p1 + (p1 - p2); bool p1floating = false; bool p2floating = false; // Compute terrain heights, clamped to the water height (and remember whether // each point was floating on water, for normal computation later) // TODO: if we ever support more than one water level per map, recompute this per point CmpPtr cmpWaterManager(*line.m_SimContext, SYSTEM_ENTITY); float w = cmpWaterManager ? cmpWaterManager->GetExactWaterLevel(p0.X, p0.Z) : 0.f; const CTerrain& terrain = line.m_SimContext->GetTerrain(); p0.Y = terrain.GetExactGroundLevel(p0.X, p0.Z); if (p0.Y < w) p0.Y = w; p1.Y = terrain.GetExactGroundLevel(p1.X, p1.Z); if (p1.Y < w) { p1.Y = w; p1floating = true; } p2.Y = terrain.GetExactGroundLevel(p2.X, p2.Z); if (p2.Y < w) { p2.Y = w; p2floating = true; } for (size_t i = 0; i < n; ++i) { // For vertex i, compute bisector of lines (i-1)..(i) and (i)..(i+1) // perpendicular to terrain normal // Normal is vertical if on water, else computed from terrain CVector3D norm; if (p1floating) norm = CVector3D(0, 1, 0); else norm = terrain.CalcExactNormal(p1.X, p1.Z); CVector3D b = ((p1 - p0).Normalized() + (p2 - p1).Normalized()).Cross(norm); // Adjust bisector length to match the line thickness, along the line's width float l = b.Dot((p2 - p1).Normalized().Cross(norm)); if (fabs(l) > 0.000001f) // avoid unlikely divide-by-zero b *= line.m_Thickness / l; // Push vertices and indices for each quad in GL_TRIANGLES order. The two triangles of each quad are indexed using // the winding orders (BR, BL, TR) and (TR, BL, TL) (where BR is bottom-right of this iteration's quad, TR top-right etc). SVertex vertex1(p1 + b + norm*OverlayRenderer::OVERLAY_VOFFSET, 0.f, v); SVertex vertex2(p1 - b + norm*OverlayRenderer::OVERLAY_VOFFSET, 1.f, v); vertices.push_back(vertex1); vertices.push_back(vertex2); u16 vertexCount = static_cast(vertices.size()); u16 index1 = vertexCount - 2; // index of vertex1 in this iteration (TR of this quad) u16 index2 = vertexCount - 1; // index of the vertex2 in this iteration (TL of this quad) if (i == 0) { // initial two vertices to continue building triangles from (n must be >= 2 for this to work) indices.push_back(index1); indices.push_back(index2); } else { u16 index1Prev = vertexCount - 4; // index of the vertex1 in the previous iteration (BR of this quad) u16 index2Prev = vertexCount - 3; // index of the vertex2 in the previous iteration (BL of this quad) ENSURE(index1Prev < vertexCount); ENSURE(index2Prev < vertexCount); // Add two corner points from last iteration and join with one of our own corners to create triangle 1 // (don't need to do this if i == 1 because i == 0 are the first two ones, they don't need to be copied) if (i > 1) { indices.push_back(index1Prev); indices.push_back(index2Prev); } indices.push_back(index1); // complete triangle 1 // create triangle 2, specifying the adjacent side's vertices in the opposite order from triangle 1 indices.push_back(index1); indices.push_back(index2Prev); indices.push_back(index2); } // alternate V coordinate for debugging v = 1 - v; // cycle the p's and compute the new p2 p0 = p1; p1 = p2; p1floating = p2floating; // if in closed mode, wrap around the coordinate array for p2 -- otherwise, extend linearly if (!closed && i == n-2) // next iteration is the last point of the line, so create an artificial p2 that extends the p0 -> p1 direction p2 = p1 + (p1 - p0); else p2 = CVector3D(line.m_Coords[(i + 2) % n].X, 0, line.m_Coords[(i + 2) % n].Y); p2.Y = terrain.GetExactGroundLevel(p2.X, p2.Z); if (p2.Y < w) { p2.Y = w; p2floating = true; } else p2floating = false; } if (closed) { // close the path if (n % 2 == 0) { u16 vertexCount = static_cast(vertices.size()); indices.push_back(vertexCount - 2); indices.push_back(vertexCount - 1); indices.push_back(0); indices.push_back(0); indices.push_back(vertexCount - 1); indices.push_back(1); } else { // add two vertices to have the good UVs for the last quad SVertex vertex1(vertices[0].m_Position, 0.f, 1.f); SVertex vertex2(vertices[1].m_Position, 1.f, 1.f); vertices.push_back(vertex1); vertices.push_back(vertex2); u16 vertexCount = static_cast(vertices.size()); indices.push_back(vertexCount - 4); indices.push_back(vertexCount - 3); indices.push_back(vertexCount - 2); indices.push_back(vertexCount - 2); indices.push_back(vertexCount - 3); indices.push_back(vertexCount - 1); } } else { // Create start and end caps. On either end, this is done by taking the centroid between the last and second-to-last pair of // vertices that was generated along the path (i.e. the vertex1's and vertex2's from above), taking a directional vector // between them, and drawing the line cap in the plane given by the two butt-end corner points plus said vector. std::vector capIndices; std::vector capVertices; // create end cap CreateLineCap( line, // the order of these vertices is important here, swapping them produces caps at the wrong side vertices[vertices.size()-2].m_Position, // top-right vertex of last quad vertices[vertices.size()-1].m_Position, // top-left vertex of last quad // directional vector between centroids of last vertex pair and second-to-last vertex pair (Centroid(vertices[vertices.size()-2], vertices[vertices.size()-1]) - Centroid(vertices[vertices.size()-4], vertices[vertices.size()-3])).Normalized(), line.m_EndCapType, capVertices, capIndices ); for (unsigned i = 0; i < capIndices.size(); i++) capIndices[i] += static_cast(vertices.size()); vertices.insert(vertices.end(), capVertices.begin(), capVertices.end()); indices.insert(indices.end(), capIndices.begin(), capIndices.end()); capIndices.clear(); capVertices.clear(); // create start cap CreateLineCap( line, // the order of these vertices is important here, swapping them produces caps at the wrong side vertices[1].m_Position, vertices[0].m_Position, // directional vector between centroids of first vertex pair and second vertex pair (Centroid(vertices[1], vertices[0]) - Centroid(vertices[3], vertices[2])).Normalized(), line.m_StartCapType, capVertices, capIndices ); for (unsigned i = 0; i < capIndices.size(); i++) capIndices[i] += static_cast(vertices.size()); vertices.insert(vertices.end(), capVertices.begin(), capVertices.end()); indices.insert(indices.end(), capIndices.begin(), capIndices.end()); } if (vertices.empty() || indices.empty()) return; // Indices for triangles, so must be multiple of 3. ENSURE(indices.size() % 3 == 0); m_BoundingBox = CBoundingBoxAligned(); for (const SVertex& vertex : vertices) m_BoundingBox += vertex.m_Position; m_VB = g_VBMan.AllocateChunk( sizeof(SVertex), vertices.size(), Renderer::Backend::IBuffer::Type::VERTEX, false); // Allocation might fail (e.g. due to too many vertices). if (m_VB) { // Copy data into backend buffer. m_VB->m_Owner->UpdateChunkVertices(m_VB.Get(), &vertices[0]); for (size_t k = 0; k < indices.size(); ++k) indices[k] += static_cast(m_VB->m_Index); m_VBIndices = g_VBMan.AllocateChunk( sizeof(u16), indices.size(), Renderer::Backend::IBuffer::Type::INDEX, false); if (m_VBIndices) m_VBIndices->m_Owner->UpdateChunkVertices(m_VBIndices.Get(), &indices[0]); } } void CTexturedLineRData::CreateLineCap(const SOverlayTexturedLine& line, const CVector3D& corner1, const CVector3D& corner2, const CVector3D& lineDirectionNormal, SOverlayTexturedLine::LineCapType endCapType, std::vector& verticesOut, std::vector& indicesOut) { if (endCapType == SOverlayTexturedLine::LINECAP_FLAT) return; // no action needed, this is the default // When not in closed mode, we've created artificial points for the start- and endpoints that extend the line in the // direction of the first and the last segment, respectively. Thus, we know both the start and endpoints have perpendicular // butt endings, i.e. the end corner vertices on either side of the line extend perpendicularly from the segment direction. // That is to say, when viewed from the top, we will have something like // . // this: and not like this: /| // ----+ / | // | / . // | / // ----+ / // int roundCapPoints = 8; // amount of points to sample along the semicircle for rounded caps (including corner points) float radius = line.m_Thickness; CVector3D centerPoint = (corner1 + corner2) * 0.5f; SVertex centerVertex(centerPoint, 0.5f, 0.5f); u16 indexOffset = static_cast(verticesOut.size()); // index offset in verticesOut from where we start adding our vertices switch (endCapType) { case SOverlayTexturedLine::LINECAP_SHARP: { roundCapPoints = 3; // creates only one point directly ahead radius *= 1.5f; // make it a bit sharper (note that we don't use the radius for the butt-end corner points so it should be ok) centerVertex.m_UVs[0] = 0.480f; // slight visual correction to make the texture match up better at the corner points } FALLTHROUGH; case SOverlayTexturedLine::LINECAP_ROUND: { // Draw a rounded line cap in the 3D plane of the line specified by the two corner points and the normal vector of the // line's direction. The terrain normal at the centroid between the two corner points is perpendicular to this plane. // The way this works is by taking a vector from the corner points' centroid to one of the corner points (which is then // of radius length), and rotate it around the terrain normal vector in that centroid. This will rotate the vector in // the line's plane, producing the desired rounded cap. // To please OpenGL's winding order, this angle needs to be negated depending on whether we start rotating from // the (center -> corner1) or (center -> corner2) vector. For the (center -> corner2) vector, we apparently need to use // the negated angle. float stepAngle = -(float)(M_PI/(roundCapPoints-1)); // Push the vertices in triangle fan order (easy to generate GL_TRIANGLES indices for afterwards) // Note that we're manually adding the corner vertices instead of having them be generated by the rotating vector. // This is because we want to support an overly large radius to make the sharp line ending look sharper. verticesOut.push_back(centerVertex); verticesOut.push_back(SVertex(corner2, 0.f, 0.f)); // Get the base vector that we will incrementally rotate in the cap plane to produce the radial sample points. // Normally corner2 - centerPoint would suffice for this since it is of radius length, but we want to support custom // radii to support tuning the 'sharpness' of sharp end caps (see above) CVector3D rotationBaseVector = (corner2 - centerPoint).Normalized() * radius; // Calculate the normal vector of the plane in which we're going to be drawing the line cap. This is the vector that // is perpendicular to both baseVector and the 'lineDirectionNormal' vector indicating the direction of the line. // Note that we shouldn't use terrain->CalcExactNormal() here because if the line is being rendered on top of water, // then CalcExactNormal will return the normal vector of the terrain that's underwater (which can be quite funky). CVector3D capPlaneNormal = lineDirectionNormal.Cross(rotationBaseVector).Normalized(); for (int i = 1; i < roundCapPoints - 1; ++i) { // Rotate the centerPoint -> corner vector by i*stepAngle radians around the cap plane normal at the center point. CQuaternion quatRotation; quatRotation.FromAxisAngle(capPlaneNormal, i * stepAngle); CVector3D worldPos3D = centerPoint + quatRotation.Rotate(rotationBaseVector); // Let v range from 0 to 1 as we move along the semi-circle, keep u fixed at 0 (i.e. curve the left vertical edge // of the texture around the edge of the semicircle) float u = 0.f; float v = Clamp((i / static_cast(roundCapPoints - 1)), 0.f, 1.f); // pos, u, v verticesOut.push_back(SVertex(worldPos3D, u, v)); } // connect back to the other butt-end corner point to complete the semicircle verticesOut.push_back(SVertex(corner1, 0.f, 1.f)); // now push indices in GL_TRIANGLES order; vertices[indexOffset] is the center vertex, vertices[indexOffset + 1] is the // first corner point, then a bunch of radial samples, and then at the end we have the other corner point again. So: for (int i=1; i < roundCapPoints; ++i) { indicesOut.push_back(indexOffset); // center vertex indicesOut.push_back(indexOffset + i); indicesOut.push_back(indexOffset + i + 1); } } break; case SOverlayTexturedLine::LINECAP_SQUARE: { // Extend the (corner1 -> corner2) vector along the direction normal and draw a square line ending consisting of // three triangles (sort of like a triangle fan) // NOTE: The order in which the vertices are pushed out determines the visibility, as they // are rendered only one-sided; the wrong order of vertices will make the cap visible only from the bottom. verticesOut.push_back(centerVertex); verticesOut.push_back(SVertex(corner2, 0.f, 0.f)); verticesOut.push_back(SVertex(corner2 + (lineDirectionNormal * (line.m_Thickness)), 0.f, 0.33333f)); // extend butt corner point 2 along the normal vector verticesOut.push_back(SVertex(corner1 + (lineDirectionNormal * (line.m_Thickness)), 0.f, 0.66666f)); // extend butt corner point 1 along the normal vector verticesOut.push_back(SVertex(corner1, 0.f, 1.0f)); // push butt corner point 1 for (int i=1; i < 4; ++i) { indicesOut.push_back(indexOffset); // center point indicesOut.push_back(indexOffset + i); indicesOut.push_back(indexOffset + i + 1); } } break; default: break; } } bool CTexturedLineRData::IsVisibleInFrustum(const CFrustum& frustum) const { return frustum.IsBoxVisible(m_BoundingBox); } Index: ps/trunk/source/renderer/WaterManager.cpp =================================================================== --- ps/trunk/source/renderer/WaterManager.cpp (revision 26900) +++ ps/trunk/source/renderer/WaterManager.cpp (revision 26901) @@ -1,1069 +1,1075 @@ /* 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 "graphics/Terrain.h" #include "graphics/TextureManager.h" #include "graphics/ShaderManager.h" #include "graphics/ShaderProgram.h" #include "lib/bits.h" #include "lib/timer.h" #include "maths/MathUtil.h" #include "maths/Vector2D.h" #include "ps/CLogger.h" #include "ps/CStrInternStatic.h" #include "ps/Game.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "renderer/backend/IDevice.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/SceneRenderer.h" #include "renderer/WaterManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpWaterManager.h" #include "simulation2/components/ICmpRangeManager.h" #include struct CoastalPoint { CoastalPoint(int idx, CVector2D pos) : index(idx), position(pos) {}; int index; CVector2D position; }; struct SWavesVertex { // vertex position CVector3D m_BasePosition; CVector3D m_ApexPosition; CVector3D m_SplashPosition; CVector3D m_RetreatPosition; CVector2D m_PerpVect; u8 m_UV[3]; // pad to a power of two u8 m_Padding[5]; }; cassert(sizeof(SWavesVertex) == 64); struct WaveObject { CVertexBufferManager::Handle m_VBVertices; CBoundingBoxAligned m_AABB; size_t m_Width; float m_TimeDiff; }; WaterManager::WaterManager() { // water m_RenderWater = false; // disabled until textures are successfully loaded m_WaterHeight = 5.0f; m_RefTextureSize = 0; m_WaterTexTimer = 0.0; m_WindAngle = 0.0f; m_Waviness = 8.0f; m_WaterColor = CColor(0.3f, 0.35f, 0.7f, 1.0f); m_WaterTint = CColor(0.28f, 0.3f, 0.59f, 1.0f); m_Murkiness = 0.45f; m_RepeatPeriod = 16.0f; m_WaterEffects = true; m_WaterFancyEffects = false; m_WaterRealDepth = false; m_WaterRefraction = false; m_WaterReflection = false; m_WaterType = L"ocean"; m_NeedsReloading = false; m_NeedInfoUpdate = true; m_MapSize = 0; m_updatei0 = 0; m_updatej0 = 0; m_updatei1 = 0; m_updatej1 = 0; } WaterManager::~WaterManager() { // Cleanup if the caller messed up UnloadWaterTextures(); m_ShoreWaves.clear(); m_ShoreWavesVBIndices.Reset(); m_DistanceHeightmap.reset(); m_WindStrength.reset(); m_FancyEffectsFramebuffer.reset(); m_RefractionFramebuffer.reset(); m_ReflectionFramebuffer.reset(); m_FancyTexture.reset(); m_FancyTextureDepth.reset(); m_ReflFboDepthTexture.reset(); m_RefrFboDepthTexture.reset(); } /////////////////////////////////////////////////////////////////// // Progressive load of water textures int WaterManager::LoadWaterTextures() { // TODO: this doesn't need to be progressive-loading any more // (since texture loading is async now) wchar_t pathname[PATH_MAX]; // Load diffuse grayscale images (for non-fancy water) for (size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); ++i) { swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/default/diffuse%02d.dds", (int)i+1); CTextureProperties textureProps(pathname); textureProps.SetAddressMode( Renderer::Backend::Sampler::AddressMode::REPEAT); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); texture->Prefetch(); m_WaterTexture[i] = texture; } m_RenderWater = true; // Load normalmaps (for fancy water) ReloadWaterNormalTextures(); // Load CoastalWaves { CTextureProperties textureProps(L"art/textures/terrain/types/water/coastalWave.png"); textureProps.SetAddressMode( Renderer::Backend::Sampler::AddressMode::REPEAT); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); texture->Prefetch(); m_WaveTex = texture; } // Load Foam { CTextureProperties textureProps(L"art/textures/terrain/types/water/foam.png"); textureProps.SetAddressMode( Renderer::Backend::Sampler::AddressMode::REPEAT); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); texture->Prefetch(); m_FoamTex = texture; } RecreateOrLoadTexturesIfNeeded(); return 0; } void WaterManager::RecreateOrLoadTexturesIfNeeded() { Renderer::Backend::IDevice* backendDevice = g_VideoMode.GetBackendDevice(); // Use screen-sized textures for minimum artifacts. const size_t newRefTextureSize = round_up_to_pow2(g_Renderer.GetHeight()); if (m_RefTextureSize != newRefTextureSize) { m_ReflectionFramebuffer.reset(); m_ReflectionTexture.reset(); m_ReflFboDepthTexture.reset(); m_RefractionFramebuffer.reset(); m_RefractionTexture.reset(); m_RefrFboDepthTexture.reset(); m_RefTextureSize = newRefTextureSize; } // Create reflection textures. const bool needsReflectionTextures = g_RenderingOptions.GetWaterEffects() && g_RenderingOptions.GetWaterReflection(); if (needsReflectionTextures && !m_ReflectionTexture) { m_ReflectionTexture = backendDevice->CreateTexture2D("WaterReflectionTexture", Renderer::Backend::Format::R8G8B8A8_UNORM, m_RefTextureSize, m_RefTextureSize, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::MIRRORED_REPEAT)); m_ReflFboDepthTexture = backendDevice->CreateTexture2D("WaterReflectionDepthTexture", Renderer::Backend::Format::D32, m_RefTextureSize, m_RefTextureSize, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::NEAREST, Renderer::Backend::Sampler::AddressMode::REPEAT)); m_ReflectionFramebuffer = backendDevice->CreateFramebuffer("ReflectionFramebuffer", m_ReflectionTexture.get(), m_ReflFboDepthTexture.get(), CColor(0.5f, 0.5f, 1.0f, 0.0f)); if (!m_ReflectionFramebuffer) { g_RenderingOptions.SetWaterReflection(false); UpdateQuality(); } } // Create refraction textures. const bool needsRefractionTextures = g_RenderingOptions.GetWaterEffects() && g_RenderingOptions.GetWaterRefraction(); if (needsRefractionTextures && !m_RefractionTexture) { m_RefractionTexture = backendDevice->CreateTexture2D("WaterRefractionTexture", Renderer::Backend::Format::R8G8B8A8_UNORM, m_RefTextureSize, m_RefTextureSize, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::MIRRORED_REPEAT)); m_RefrFboDepthTexture = backendDevice->CreateTexture2D("WaterRefractionDepthTexture", Renderer::Backend::Format::D32, m_RefTextureSize, m_RefTextureSize, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::NEAREST, Renderer::Backend::Sampler::AddressMode::REPEAT)); m_RefractionFramebuffer = backendDevice->CreateFramebuffer("RefractionFramebuffer", m_RefractionTexture.get(), m_RefrFboDepthTexture.get(), CColor(1.0f, 0.0f, 0.0f, 0.0f)); if (!m_RefractionFramebuffer) { g_RenderingOptions.SetWaterRefraction(false); UpdateQuality(); } } const uint32_t newWidth = static_cast(g_Renderer.GetWidth()); const uint32_t newHeight = static_cast(g_Renderer.GetHeight()); if (m_FancyTexture && (m_FancyTexture->GetWidth() != newWidth || m_FancyTexture->GetHeight() != newHeight)) { m_FancyEffectsFramebuffer.reset(); m_FancyTexture.reset(); m_FancyTextureDepth.reset(); } // Create the Fancy Effects textures. const bool needsFancyTextures = g_RenderingOptions.GetWaterEffects() && g_RenderingOptions.GetWaterFancyEffects(); if (needsFancyTextures && !m_FancyTexture) { m_FancyTexture = backendDevice->CreateTexture2D("WaterFancyTexture", Renderer::Backend::Format::R8G8B8A8_UNORM, g_Renderer.GetWidth(), g_Renderer.GetHeight(), Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::REPEAT)); m_FancyTextureDepth = backendDevice->CreateTexture2D("WaterFancyDepthTexture", Renderer::Backend::Format::D32, g_Renderer.GetWidth(), g_Renderer.GetHeight(), Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::REPEAT)); m_FancyEffectsFramebuffer = backendDevice->CreateFramebuffer("FancyEffectsFramebuffer", m_FancyTexture.get(), m_FancyTextureDepth.get()); if (!m_FancyEffectsFramebuffer) { g_RenderingOptions.SetWaterRefraction(false); UpdateQuality(); } } } void WaterManager::ReloadWaterNormalTextures() { wchar_t pathname[PATH_MAX]; for (size_t i = 0; i < ARRAY_SIZE(m_NormalMap); ++i) { swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/%ls/normal00%02d.png", m_WaterType.c_str(), static_cast(i) + 1); CTextureProperties textureProps(pathname); textureProps.SetAddressMode( Renderer::Backend::Sampler::AddressMode::REPEAT); textureProps.SetAnisotropicFilter(true); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); texture->Prefetch(); m_NormalMap[i] = texture; } } /////////////////////////////////////////////////////////////////// // Unload water textures void WaterManager::UnloadWaterTextures() { for (size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); i++) m_WaterTexture[i].reset(); for (size_t i = 0; i < ARRAY_SIZE(m_NormalMap); i++) m_NormalMap[i].reset(); m_RefractionFramebuffer.reset(); m_ReflectionFramebuffer.reset(); m_ReflectionTexture.reset(); m_RefractionTexture.reset(); } template static inline void ComputeDirection(float* distanceMap, const u16* heightmap, float waterHeight, size_t SideSize, size_t maxLevel) { #define ABOVEWATER(x, z) (HEIGHT_SCALE * heightmap[z*SideSize + x] >= waterHeight) #define UPDATELOOKAHEAD \ for (; lookahead <= id2+maxLevel && lookahead < SideSize && \ ((!Transpose && !ABOVEWATER(lookahead, id1)) || (Transpose && !ABOVEWATER(id1, lookahead))); ++lookahead) // Algorithm: // We want to know the distance to the closest shore point. Go through each line/column, // keep track of when we encountered the last shore point and how far ahead the next one is. for (size_t id1 = 0; id1 < SideSize; ++id1) { size_t id2 = 0; const size_t& x = Transpose ? id1 : id2; const size_t& z = Transpose ? id2 : id1; size_t level = ABOVEWATER(x, z) ? 0 : maxLevel; size_t lookahead = (size_t)(level > 0); UPDATELOOKAHEAD; // start moving for (; id2 < SideSize; ++id2) { // update current level if (ABOVEWATER(x, z)) level = 0; else level = std::min(level+1, maxLevel); // move lookahead if (lookahead == id2) ++lookahead; UPDATELOOKAHEAD; // This is the important bit: set the distance to either: // - the distance to the previous shore point (level) // - the distance to the next shore point (lookahead-id2) distanceMap[z*SideSize + x] = std::min(distanceMap[z*SideSize + x], (float)std::min(lookahead-id2, level)); } } #undef ABOVEWATER #undef UPDATELOOKAHEAD } /////////////////////////////////////////////////////////////////// // Calculate our binary heightmap from the terrain heightmap. void WaterManager::RecomputeDistanceHeightmap() { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); if (!terrain || !terrain->GetHeightMap()) return; size_t SideSize = m_MapSize; // we want to look ahead some distance, but not too much (less efficient and not interesting). This is our lookahead. const size_t maxLevel = 5; if (!m_DistanceHeightmap) { m_DistanceHeightmap = std::make_unique(SideSize * SideSize); std::fill(m_DistanceHeightmap.get(), m_DistanceHeightmap.get() + SideSize * SideSize, static_cast(maxLevel)); } // Create a manhattan-distance heightmap. // This could be refined to only be done near the coast itself, but it's probably not necessary. u16* heightmap = terrain->GetHeightMap(); ComputeDirection(m_DistanceHeightmap.get(), heightmap, m_WaterHeight, SideSize, maxLevel); ComputeDirection(m_DistanceHeightmap.get(), heightmap, m_WaterHeight, SideSize, maxLevel); } // This requires m_DistanceHeightmap to be defined properly. void WaterManager::CreateWaveMeshes() { if (m_MapSize == 0) return; CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); if (!terrain || !terrain->GetHeightMap()) return; m_ShoreWaves.clear(); m_ShoreWavesVBIndices.Reset(); if (m_Waviness < 5.0f && m_WaterType != L"ocean") return; size_t SideSize = m_MapSize; // First step: get the points near the coast. std::set CoastalPointsSet; for (size_t z = 1; z < SideSize-1; ++z) for (size_t x = 1; x < SideSize-1; ++x) // get the points not on the shore but near it, ocean-side if (m_DistanceHeightmap[z*m_MapSize + x] > 0.5f && m_DistanceHeightmap[z*m_MapSize + x] < 1.5f) CoastalPointsSet.insert((z)*SideSize + x); // Second step: create chains out of those coastal points. static const int around[8][2] = { { -1,-1 }, { -1,0 }, { -1,1 }, { 0,1 }, { 1,1 }, { 1,0 }, { 1,-1 }, { 0,-1 } }; std::vector > CoastalPointsChains; while (!CoastalPointsSet.empty()) { int index = *(CoastalPointsSet.begin()); int x = index % SideSize; int y = (index - x ) / SideSize; std::deque Chain; Chain.push_front(CoastalPoint(index,CVector2D(x*4,y*4))); // Erase us. CoastalPointsSet.erase(CoastalPointsSet.begin()); // We're our starter points. At most we can have 2 points close to us. // We'll pick the first one and look for its neighbors (he can only have one new) // Up until we either reach the end of the chain, or ourselves. // Then go down the other direction if there is any. int neighbours[2] = { -1, -1 }; int nbNeighb = 0; for (int i = 0; i < 8; ++i) { if (CoastalPointsSet.count(x + around[i][0] + (y + around[i][1])*SideSize)) { if (nbNeighb < 2) neighbours[nbNeighb] = x + around[i][0] + (y + around[i][1])*SideSize; ++nbNeighb; } } if (nbNeighb > 2) continue; for (int i = 0; i < 2; ++i) { if (neighbours[i] == -1) continue; // Move to our neighboring point int xx = neighbours[i] % SideSize; int yy = (neighbours[i] - xx ) / SideSize; int indexx = xx + yy*SideSize; int endedChain = false; if (i == 0) Chain.push_back(CoastalPoint(indexx,CVector2D(xx*4,yy*4))); else Chain.push_front(CoastalPoint(indexx,CVector2D(xx*4,yy*4))); // If there's a loop we'll be the "other" neighboring point already so check for that. // We'll readd at the end/front the other one to have full squares. if (CoastalPointsSet.count(indexx) == 0) break; CoastalPointsSet.erase(indexx); // Start checking from there. while(!endedChain) { bool found = false; nbNeighb = 0; for (int p = 0; p < 8; ++p) { if (CoastalPointsSet.count(xx+around[p][0] + (yy + around[p][1])*SideSize)) { if (nbNeighb >= 2) { CoastalPointsSet.erase(xx + yy*SideSize); continue; } ++nbNeighb; // We've found a new point around us. // Move there xx = xx + around[p][0]; yy = yy + around[p][1]; indexx = xx + yy*SideSize; if (i == 0) Chain.push_back(CoastalPoint(indexx,CVector2D(xx*4,yy*4))); else Chain.push_front(CoastalPoint(indexx,CVector2D(xx*4,yy*4))); CoastalPointsSet.erase(xx + yy*SideSize); found = true; break; } } if (!found) endedChain = true; } } if (Chain.size() > 10) CoastalPointsChains.push_back(Chain); } // (optional) third step: Smooth chains out. // This is also really dumb. for (size_t i = 0; i < CoastalPointsChains.size(); ++i) { // Bump 1 for smoother. for (int p = 0; p < 3; ++p) { for (size_t j = 1; j < CoastalPointsChains[i].size()-1; ++j) { CVector2D realPos = CoastalPointsChains[i][j-1].position + CoastalPointsChains[i][j+1].position; CoastalPointsChains[i][j].position = (CoastalPointsChains[i][j].position + realPos/2.0f)/2.0f; } } } // Fourth step: create waves themselves, using those chains. We basically create subchains. GLushort waveSizes = 14; // maximal size in width. // Construct indices buffer (we can afford one for all of them) std::vector water_indices; for (GLushort a = 0; a < waveSizes - 1; ++a) { for (GLushort rect = 0; rect < 7; ++rect) { water_indices.push_back(a * 9 + rect); water_indices.push_back(a * 9 + 9 + rect); water_indices.push_back(a * 9 + 1 + rect); water_indices.push_back(a * 9 + 9 + rect); water_indices.push_back(a * 9 + 10 + rect); water_indices.push_back(a * 9 + 1 + rect); } } // Generic indexes, max-length m_ShoreWavesVBIndices = g_VBMan.AllocateChunk( sizeof(GLushort), water_indices.size(), Renderer::Backend::IBuffer::Type::INDEX, false, nullptr, CVertexBufferManager::Group::WATER); m_ShoreWavesVBIndices->m_Owner->UpdateChunkVertices(m_ShoreWavesVBIndices.Get(), &water_indices[0]); float diff = (rand() % 50) / 5.0f; std::vector vertices, reversed; for (size_t i = 0; i < CoastalPointsChains.size(); ++i) { for (size_t j = 0; j < CoastalPointsChains[i].size()-waveSizes; ++j) { if (CoastalPointsChains[i].size()- 1 - j < waveSizes) break; GLushort width = waveSizes; // First pass to get some parameters out. float outmost = 0.0f; // how far to move on the shore. float avgDepth = 0.0f; int sign = 1; CVector2D firstPerp(0,0), perp(0,0), lastPerp(0,0); for (GLushort a = 0; a < waveSizes;++a) { lastPerp = perp; perp = CVector2D(0,0); int nb = 0; CVector2D pos = CoastalPointsChains[i][j+a].position; CVector2D posPlus; CVector2D posMinus; if (a > 0) { ++nb; posMinus = CoastalPointsChains[i][j+a-1].position; perp += pos-posMinus; } if (a < waveSizes-1) { ++nb; posPlus = CoastalPointsChains[i][j+a+1].position; perp += posPlus-pos; } perp /= nb; perp = CVector2D(-perp.Y,perp.X).Normalized(); if (a == 0) firstPerp = perp; if ( a > 1 && perp.Dot(lastPerp) < 0.90f && perp.Dot(firstPerp) < 0.70f) { width = a+1; break; } if (terrain->GetExactGroundLevel(pos.X+perp.X*1.5f, pos.Y+perp.Y*1.5f) > m_WaterHeight) sign = -1; avgDepth += terrain->GetExactGroundLevel(pos.X+sign*perp.X*20.0f, pos.Y+sign*perp.Y*20.0f) - m_WaterHeight; float localOutmost = -2.0f; while (localOutmost < 0.0f) { float depth = terrain->GetExactGroundLevel(pos.X+sign*perp.X*localOutmost, pos.Y+sign*perp.Y*localOutmost) - m_WaterHeight; if (depth < 0.0f || depth > 0.6f) localOutmost += 0.2f; else break; } outmost += localOutmost; } if (width < 5) { j += 6; continue; } outmost /= width; if (outmost > -0.5f) { j += 3; continue; } outmost = -2.5f + outmost * m_Waviness/10.0f; avgDepth /= width; if (avgDepth > -1.3f) { j += 3; continue; } // we passed the checks, we can create a wave of size "width". std::unique_ptr shoreWave = std::make_unique(); vertices.clear(); vertices.reserve(9 * width); shoreWave->m_Width = width; shoreWave->m_TimeDiff = diff; diff += (rand() % 100) / 25.0f + 4.0f; for (GLushort a = 0; a < width;++a) { perp = CVector2D(0,0); int nb = 0; CVector2D pos = CoastalPointsChains[i][j+a].position; CVector2D posPlus; CVector2D posMinus; if (a > 0) { ++nb; posMinus = CoastalPointsChains[i][j+a-1].position; perp += pos-posMinus; } if (a < waveSizes-1) { ++nb; posPlus = CoastalPointsChains[i][j+a+1].position; perp += posPlus-pos; } perp /= nb; perp = CVector2D(-perp.Y,perp.X).Normalized(); SWavesVertex point[9]; float baseHeight = 0.04f; float halfWidth = (width-1.0f)/2.0f; float sideNess = sqrtf(Clamp( (halfWidth - fabsf(a - halfWidth)) / 3.0f, 0.0f, 1.0f)); point[0].m_UV[0] = a; point[0].m_UV[1] = 8; point[1].m_UV[0] = a; point[1].m_UV[1] = 7; point[2].m_UV[0] = a; point[2].m_UV[1] = 6; point[3].m_UV[0] = a; point[3].m_UV[1] = 5; point[4].m_UV[0] = a; point[4].m_UV[1] = 4; point[5].m_UV[0] = a; point[5].m_UV[1] = 3; point[6].m_UV[0] = a; point[6].m_UV[1] = 2; point[7].m_UV[0] = a; point[7].m_UV[1] = 1; point[8].m_UV[0] = a; point[8].m_UV[1] = 0; point[0].m_PerpVect = perp; point[1].m_PerpVect = perp; point[2].m_PerpVect = perp; point[3].m_PerpVect = perp; point[4].m_PerpVect = perp; point[5].m_PerpVect = perp; point[6].m_PerpVect = perp; point[7].m_PerpVect = perp; point[8].m_PerpVect = perp; static const float perpT1[9] = { 6.0f, 6.05f, 6.1f, 6.2f, 6.3f, 6.4f, 6.5f, 6.6f, 9.7f }; static const float perpT2[9] = { 2.0f, 2.1f, 2.2f, 2.3f, 2.4f, 3.0f, 3.3f, 3.6f, 9.5f }; static const float perpT3[9] = { 1.1f, 0.7f, -0.2f, 0.0f, 0.6f, 1.3f, 2.2f, 3.6f, 9.0f }; static const float perpT4[9] = { 2.0f, 2.1f, 1.2f, 1.5f, 1.7f, 1.9f, 2.7f, 3.8f, 9.0f }; static const float heightT1[9] = { 0.0f, 0.2f, 0.5f, 0.8f, 0.9f, 0.85f, 0.6f, 0.2f, 0.0 }; static const float heightT2[9] = { -0.8f, -0.4f, 0.0f, 0.1f, 0.1f, 0.03f, 0.0f, 0.0f, 0.0 }; static const float heightT3[9] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0 }; for (size_t t = 0; t < 9; ++t) { float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT1[t]+outmost), pos.Y+sign*perp.Y*(perpT1[t]+outmost)); point[t].m_BasePosition = CVector3D(pos.X+sign*perp.X*(perpT1[t]+outmost), baseHeight + heightT1[t]*sideNess + std::max(m_WaterHeight,terrHeight), pos.Y+sign*perp.Y*(perpT1[t]+outmost)); } for (size_t t = 0; t < 9; ++t) { float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT2[t]+outmost), pos.Y+sign*perp.Y*(perpT2[t]+outmost)); point[t].m_ApexPosition = CVector3D(pos.X+sign*perp.X*(perpT2[t]+outmost), baseHeight + heightT1[t]*sideNess + std::max(m_WaterHeight,terrHeight), pos.Y+sign*perp.Y*(perpT2[t]+outmost)); } for (size_t t = 0; t < 9; ++t) { float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT3[t]+outmost*sideNess), pos.Y+sign*perp.Y*(perpT3[t]+outmost*sideNess)); point[t].m_SplashPosition = CVector3D(pos.X+sign*perp.X*(perpT3[t]+outmost*sideNess), baseHeight + heightT2[t]*sideNess + std::max(m_WaterHeight,terrHeight), pos.Y+sign*perp.Y*(perpT3[t]+outmost*sideNess)); } for (size_t t = 0; t < 9; ++t) { float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT4[t]+outmost), pos.Y+sign*perp.Y*(perpT4[t]+outmost)); point[t].m_RetreatPosition = CVector3D(pos.X+sign*perp.X*(perpT4[t]+outmost), baseHeight + heightT3[t]*sideNess + std::max(m_WaterHeight,terrHeight), pos.Y+sign*perp.Y*(perpT4[t]+outmost)); } vertices.push_back(point[8]); vertices.push_back(point[7]); vertices.push_back(point[6]); vertices.push_back(point[5]); vertices.push_back(point[4]); vertices.push_back(point[3]); vertices.push_back(point[2]); vertices.push_back(point[1]); vertices.push_back(point[0]); shoreWave->m_AABB += point[8].m_SplashPosition; shoreWave->m_AABB += point[8].m_BasePosition; shoreWave->m_AABB += point[0].m_SplashPosition; shoreWave->m_AABB += point[0].m_BasePosition; shoreWave->m_AABB += point[4].m_ApexPosition; } if (sign == 1) { // Let's do some fancy reversing. reversed.clear(); reversed.reserve(vertices.size()); for (int a = width - 1; a >= 0; --a) { for (size_t t = 0; t < 9; ++t) reversed.push_back(vertices[a * 9 + t]); } std::swap(vertices, reversed); } j += width/2-1; shoreWave->m_VBVertices = g_VBMan.AllocateChunk( sizeof(SWavesVertex), vertices.size(), Renderer::Backend::IBuffer::Type::VERTEX, false, nullptr, CVertexBufferManager::Group::WATER); shoreWave->m_VBVertices->m_Owner->UpdateChunkVertices(shoreWave->m_VBVertices.Get(), &vertices[0]); m_ShoreWaves.emplace_back(std::move(shoreWave)); } } } void WaterManager::RenderWaves( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CFrustum& frustrum) { GPU_SCOPED_LABEL(deviceCommandContext, "Render Waves"); if (!m_WaterFancyEffects) return; deviceCommandContext->SetGraphicsPipelineState( Renderer::Backend::MakeDefaultGraphicsPipelineStateDesc()); deviceCommandContext->SetFramebuffer(m_FancyEffectsFramebuffer.get()); deviceCommandContext->ClearFramebuffer(); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_water_waves); deviceCommandContext->SetGraphicsPipelineState( tech->GetGraphicsPipelineStateDesc()); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = tech->GetShader(); m_WaveTex->UploadBackendTextureIfNeeded(deviceCommandContext); m_FoamTex->UploadBackendTextureIfNeeded(deviceCommandContext); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_waveTex), m_WaveTex->GetBackendTexture()); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_foamTex), m_FoamTex->GetBackendTexture()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_time), static_cast(m_WaterTexTimer)); const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection(); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), transform.AsFloatArray()); for (size_t a = 0; a < m_ShoreWaves.size(); ++a) { if (!frustrum.IsBoxVisible(m_ShoreWaves[a]->m_AABB)) continue; CVertexBuffer::VBChunk* VBchunk = m_ShoreWaves[a]->m_VBVertices.Get(); VBchunk->m_Owner->UploadIfNeeded(deviceCommandContext); m_ShoreWavesVBIndices->m_Owner->UploadIfNeeded(deviceCommandContext); const uint32_t stride = sizeof(SWavesVertex); const uint32_t firstVertexOffset = VBchunk->m_Index * stride; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, Renderer::Backend::Format::R32G32B32_SFLOAT, - firstVertexOffset + offsetof(SWavesVertex, m_BasePosition), stride, 0); + firstVertexOffset + offsetof(SWavesVertex, m_BasePosition), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::NORMAL, Renderer::Backend::Format::R32G32_SFLOAT, - firstVertexOffset + offsetof(SWavesVertex, m_PerpVect), stride, 0); + firstVertexOffset + offsetof(SWavesVertex, m_PerpVect), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, Renderer::Backend::Format::R8G8_UINT, - firstVertexOffset + offsetof(SWavesVertex, m_UV), stride, 0); + firstVertexOffset + offsetof(SWavesVertex, m_UV), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV1, Renderer::Backend::Format::R32G32B32_SFLOAT, - firstVertexOffset + offsetof(SWavesVertex, m_ApexPosition), stride, 0); + firstVertexOffset + offsetof(SWavesVertex, m_ApexPosition), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV2, Renderer::Backend::Format::R32G32B32_SFLOAT, - firstVertexOffset + offsetof(SWavesVertex, m_SplashPosition), stride, 0); + firstVertexOffset + offsetof(SWavesVertex, m_SplashPosition), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV3, Renderer::Backend::Format::R32G32B32_SFLOAT, - firstVertexOffset + offsetof(SWavesVertex, m_RetreatPosition), stride, 0); + firstVertexOffset + offsetof(SWavesVertex, m_RetreatPosition), stride, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_translation), m_ShoreWaves[a]->m_TimeDiff); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_width), static_cast(m_ShoreWaves[a]->m_Width)); deviceCommandContext->SetVertexBuffer(0, VBchunk->m_Owner->GetBuffer()); deviceCommandContext->SetIndexBuffer(m_ShoreWavesVBIndices->m_Owner->GetBuffer()); const uint32_t indexCount = (m_ShoreWaves[a]->m_Width - 1) * (7 * 6); deviceCommandContext->DrawIndexed(m_ShoreWavesVBIndices->m_Index, indexCount, 0); g_Renderer.GetStats().m_DrawCalls++; g_Renderer.GetStats().m_WaterTris += indexCount / 3; } deviceCommandContext->EndPass(); deviceCommandContext->SetFramebuffer( deviceCommandContext->GetDevice()->GetCurrentBackbuffer()); } void WaterManager::RecomputeWaterData() { if (!m_MapSize) return; RecomputeDistanceHeightmap(); RecomputeWindStrength(); CreateWaveMeshes(); } /////////////////////////////////////////////////////////////////// // Calculate the strength of the wind at a given point on the map. void WaterManager::RecomputeWindStrength() { if (m_MapSize <= 0) return; if (!m_WindStrength) m_WindStrength = std::make_unique(m_MapSize * m_MapSize); CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); if (!terrain || !terrain->GetHeightMap()) return; CVector2D windDir = CVector2D(cos(m_WindAngle), sin(m_WindAngle)); int stepSize = 10; ssize_t windX = -round(stepSize * windDir.X); ssize_t windY = -round(stepSize * windDir.Y); struct SWindPoint { SWindPoint(size_t x, size_t y, float strength) : X(x), Y(y), windStrength(strength) {} ssize_t X; ssize_t Y; float windStrength; }; std::vector startingPoints; std::vector> movement; // Every increment, move each starting point by all of these. // Compute starting points (one or two edges of the map) and how much to move each computation increment. if (fabs(windDir.X) < 0.01f) { movement.emplace_back(0, windY > 0.f ? 1 : -1); startingPoints.reserve(m_MapSize); size_t start = windY > 0 ? 0 : m_MapSize - 1; for (size_t x = 0; x < m_MapSize; ++x) startingPoints.emplace_back(x, start, 0.f); } else if (fabs(windDir.Y) < 0.01f) { movement.emplace_back(windX > 0.f ? 1 : - 1, 0); startingPoints.reserve(m_MapSize); size_t start = windX > 0 ? 0 : m_MapSize - 1; for (size_t z = 0; z < m_MapSize; ++z) startingPoints.emplace_back(start, z, 0.f); } else { startingPoints.reserve(m_MapSize * 2); // Points along X. size_t start = windY > 0 ? 0 : m_MapSize - 1; for (size_t x = 0; x < m_MapSize; ++x) startingPoints.emplace_back(x, start, 0.f); // Points along Z, avoid repeating the corner point. start = windX > 0 ? 0 : m_MapSize - 1; if (windY > 0) for (size_t z = 1; z < m_MapSize; ++z) startingPoints.emplace_back(start, z, 0.f); else for (size_t z = 0; z < m_MapSize-1; ++z) startingPoints.emplace_back(start, z, 0.f); // Compute movement array. movement.reserve(std::max(std::abs(windX),std::abs(windY))); while (windX != 0 || windY != 0) { std::pair move = { windX == 0 ? 0 : windX > 0 ? +1 : -1, windY == 0 ? 0 : windY > 0 ? +1 : -1 }; windX -= move.first; windY -= move.second; movement.push_back(move); } } // We have all starting points ready, move them all until the map is covered. for (SWindPoint& point : startingPoints) { // Starting velocity is 1.0 unless in shallow water. m_WindStrength[point.Y * m_MapSize + point.X] = 1.f; float depth = m_WaterHeight - terrain->GetVertexGroundLevel(point.X, point.Y); if (depth > 0.f && depth < 2.f) m_WindStrength[point.Y * m_MapSize + point.X] = depth / 2.f; point.windStrength = m_WindStrength[point.Y * m_MapSize + point.X]; bool onMap = true; while (onMap) for (size_t step = 0; step < movement.size(); ++step) { // Move wind speed towards the mean. point.windStrength = 0.15f + point.windStrength * 0.85f; // Adjust speed based on height difference, a positive height difference slowly increases speed (simulate venturi effect) // and a lower height reduces speed (wind protection from hills/...) float heightDiff = std::max(m_WaterHeight, terrain->GetVertexGroundLevel(point.X + movement[step].first, point.Y + movement[step].second)) - std::max(m_WaterHeight, terrain->GetVertexGroundLevel(point.X, point.Y)); if (heightDiff > 0.f) point.windStrength = std::min(2.f, point.windStrength + std::min(4.f, heightDiff) / 40.f); else point.windStrength = std::max(0.f, point.windStrength + std::max(-4.f, heightDiff) / 5.f); point.X += movement[step].first; point.Y += movement[step].second; if (point.X < 0 || point.X >= static_cast(m_MapSize) || point.Y < 0 || point.Y >= static_cast(m_MapSize)) { onMap = false; break; } m_WindStrength[point.Y * m_MapSize + point.X] = point.windStrength; } } // TODO: should perhaps blur a little, or change the above code to incorporate neighboring tiles a bit. } //////////////////////////////////////////////////////////////////////// // TODO: This will always recalculate for now void WaterManager::SetMapSize(size_t size) { // TODO: Im' blindly trusting the user here. m_MapSize = size; m_NeedInfoUpdate = true; m_updatei0 = 0; m_updatei1 = size; m_updatej0 = 0; m_updatej1 = size; m_DistanceHeightmap.reset(); m_WindStrength.reset(); } //////////////////////////////////////////////////////////////////////// // This will set the bools properly void WaterManager::UpdateQuality() { if (g_RenderingOptions.GetWaterEffects() != m_WaterEffects) { m_WaterEffects = g_RenderingOptions.GetWaterEffects(); m_NeedsReloading = true; } if (g_RenderingOptions.GetWaterFancyEffects() != m_WaterFancyEffects) { m_WaterFancyEffects = g_RenderingOptions.GetWaterFancyEffects(); m_NeedsReloading = true; } if (g_RenderingOptions.GetWaterRealDepth() != m_WaterRealDepth) { m_WaterRealDepth = g_RenderingOptions.GetWaterRealDepth(); m_NeedsReloading = true; } if (g_RenderingOptions.GetWaterRefraction() != m_WaterRefraction) { m_WaterRefraction = g_RenderingOptions.GetWaterRefraction(); m_NeedsReloading = true; } if (g_RenderingOptions.GetWaterReflection() != m_WaterReflection) { m_WaterReflection = g_RenderingOptions.GetWaterReflection(); m_NeedsReloading = true; } } bool WaterManager::WillRenderFancyWater() const { return m_RenderWater && g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB && g_RenderingOptions.GetWaterEffects(); } size_t WaterManager::GetCurrentTextureIndex(const double& period) const { ENSURE(period > 0.0); return static_cast(m_WaterTexTimer * ARRAY_SIZE(m_WaterTexture) / period) % ARRAY_SIZE(m_WaterTexture); } size_t WaterManager::GetNextTextureIndex(const double& period) const { ENSURE(period > 0.0); return (GetCurrentTextureIndex(period) + 1) % ARRAY_SIZE(m_WaterTexture); } Index: ps/trunk/source/renderer/backend/IDevice.h =================================================================== --- ps/trunk/source/renderer/backend/IDevice.h (revision 26900) +++ ps/trunk/source/renderer/backend/IDevice.h (revision 26901) @@ -1,108 +1,109 @@ /* 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/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 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 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 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 Present() = 0; virtual bool IsTextureFormatSupported(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/IDeviceCommandContext.h =================================================================== --- ps/trunk/source/renderer/backend/IDeviceCommandContext.h (revision 26900) +++ ps/trunk/source/renderer/backend/IDeviceCommandContext.h (revision 26901) @@ -1,157 +1,166 @@ /* 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_IDEVICECOMMANDCONTEXT #define INCLUDED_RENDERER_BACKEND_IDEVICECOMMANDCONTEXT #include "ps/containers/Span.h" #include "renderer/backend/Format.h" #include "renderer/backend/IDeviceObject.h" #include "renderer/backend/PipelineState.h" #include #include namespace Renderer { namespace Backend { class IBuffer; class IDevice; class IFramebuffer; class ITexture; class IDeviceCommandContext : public IDeviceObject { public: virtual void SetGraphicsPipelineState(const GraphicsPipelineStateDesc& pipelineStateDesc) = 0; virtual void BlitFramebuffer( IFramebuffer* destinationFramebuffer, IFramebuffer* sourceFramebuffer) = 0; virtual void ClearFramebuffer() = 0; virtual void ClearFramebuffer(const bool color, const bool depth, const bool stencil) = 0; virtual void SetFramebuffer(IFramebuffer* framebuffer) = 0; virtual void ReadbackFramebufferSync( const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height, void* data) = 0; virtual void UploadTexture(ITexture* texture, const Format dataFormat, const void* data, const size_t dataSize, const uint32_t level = 0, const uint32_t layer = 0) = 0; virtual void UploadTextureRegion(ITexture* texture, const Format dataFormat, const void* data, const size_t dataSize, const uint32_t xOffset, const uint32_t yOffset, const uint32_t width, const uint32_t height, const uint32_t level = 0, const uint32_t layer = 0) = 0; using UploadBufferFunction = std::function; virtual void UploadBuffer(IBuffer* buffer, const void* data, const uint32_t dataSize) = 0; virtual void UploadBuffer(IBuffer* buffer, const UploadBufferFunction& uploadFunction) = 0; virtual void UploadBufferRegion( IBuffer* buffer, const void* data, const uint32_t dataOffset, const uint32_t dataSize) = 0; virtual void UploadBufferRegion( IBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize, const UploadBufferFunction& uploadFunction) = 0; // TODO: maybe we should add a more common type, like CRectI. struct Rect { int32_t x, y; int32_t width, height; }; virtual void SetScissors(const uint32_t scissorCount, const Rect* scissors) = 0; virtual void SetViewports(const uint32_t viewportCount, const Rect* viewports) = 0; virtual void SetVertexAttributeFormat( const VertexAttributeStream stream, const Format format, const uint32_t offset, const uint32_t stride, + const VertexAttributeRate rate, const uint32_t bindingSlot) = 0; virtual void SetVertexBuffer(const uint32_t bindingSlot, IBuffer* buffer) = 0; - virtual void SetVertexBufferData(const uint32_t bindingSlot, const void* data) = 0; + virtual void SetVertexBufferData( + const uint32_t bindingSlot, const void* data, const uint32_t dataSize) = 0; virtual void SetIndexBuffer(IBuffer* buffer) = 0; - virtual void SetIndexBufferData(const void* data) = 0; + virtual void SetIndexBufferData(const void* data, const uint32_t dataSize) = 0; virtual void BeginPass() = 0; virtual void EndPass() = 0; virtual void Draw(const uint32_t firstVertex, const uint32_t vertexCount) = 0; virtual void DrawIndexed( const uint32_t firstIndex, const uint32_t indexCount, const int32_t vertexOffset) = 0; + virtual void DrawInstanced( + const uint32_t firstVertex, const uint32_t vertexCount, + const uint32_t firstInstance, const uint32_t instanceCount) = 0; + virtual void DrawIndexedInstanced( + const uint32_t firstIndex, const uint32_t indexCount, + const uint32_t firstInstance, const uint32_t instanceCount, + const int32_t vertexOffset) = 0; // TODO: should be removed when performance impact is minimal on slow hardware. virtual void DrawIndexedInRange( const uint32_t firstIndex, const uint32_t indexCount, const uint32_t start, const uint32_t end) = 0; virtual void SetTexture(const int32_t bindingSlot, ITexture* texture) = 0; virtual void SetUniform( const int32_t bindingSlot, const float value) = 0; virtual void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY) = 0; virtual void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ) = 0; virtual void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ, const float valueW) = 0; virtual void SetUniform( const int32_t bindingSlot, PS::span values) = 0; virtual void BeginScopedLabel(const char* name) = 0; virtual void EndScopedLabel() = 0; virtual void Flush() = 0; }; } // namespace Backend } // namespace Renderer #define GPU_SCOPED_LABEL(deviceCommandContext, name) \ GPUScopedLabel scopedLabel((deviceCommandContext), (name)); class GPUScopedLabel { public: GPUScopedLabel( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const char* name) : m_DeviceCommandContext(deviceCommandContext) { m_DeviceCommandContext->BeginScopedLabel(name); } ~GPUScopedLabel() { m_DeviceCommandContext->EndScopedLabel(); } private: Renderer::Backend::IDeviceCommandContext* m_DeviceCommandContext = nullptr; }; #endif // INCLUDED_RENDERER_BACKEND_IDEVICECOMMANDCONTEXT Index: ps/trunk/source/renderer/backend/dummy/Device.cpp =================================================================== --- ps/trunk/source/renderer/backend/dummy/Device.cpp (revision 26900) +++ ps/trunk/source/renderer/backend/dummy/Device.cpp (revision 26901) @@ -1,128 +1,129 @@ /* 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 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, format, width, height, MIPLevelCount); } std::unique_ptr CDevice::CreateTexture2D(const char* name, 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, 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::Present() { // We have nothing to present. } bool CDevice::IsTextureFormatSupported(const Format UNUSED(format)) const { return true; } } // namespace Dummy } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/dummy/DeviceCommandContext.cpp =================================================================== --- ps/trunk/source/renderer/backend/dummy/DeviceCommandContext.cpp (revision 26900) +++ ps/trunk/source/renderer/backend/dummy/DeviceCommandContext.cpp (revision 26901) @@ -1,204 +1,215 @@ /* 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 "DeviceCommandContext.h" #include "renderer/backend/dummy/Buffer.h" #include "renderer/backend/dummy/Device.h" #include "renderer/backend/dummy/Framebuffer.h" #include "renderer/backend/dummy/ShaderProgram.h" #include "renderer/backend/dummy/Texture.h" namespace Renderer { namespace Backend { namespace Dummy { // static std::unique_ptr CDeviceCommandContext::Create(CDevice* device) { std::unique_ptr deviceCommandContext(new CDeviceCommandContext()); deviceCommandContext->m_Device = device; return deviceCommandContext; } CDeviceCommandContext::CDeviceCommandContext() = default; CDeviceCommandContext::~CDeviceCommandContext() = default; IDevice* CDeviceCommandContext::GetDevice() { return m_Device; } void CDeviceCommandContext::SetGraphicsPipelineState( const GraphicsPipelineStateDesc&) { } void CDeviceCommandContext::UploadTexture( ITexture*, const Format, const void*, const size_t, const uint32_t, const uint32_t) { } void CDeviceCommandContext::UploadTextureRegion( ITexture*, const Format, const void*, const size_t, const uint32_t, const uint32_t, const uint32_t, const uint32_t, const uint32_t, const uint32_t) { } void CDeviceCommandContext::UploadBuffer(IBuffer*, const void*, const uint32_t) { } void CDeviceCommandContext::UploadBuffer(IBuffer*, const UploadBufferFunction&) { } void CDeviceCommandContext::UploadBufferRegion( IBuffer*, const void*, const uint32_t, const uint32_t) { } void CDeviceCommandContext::UploadBufferRegion( IBuffer*, const uint32_t, const uint32_t, const UploadBufferFunction&) { } void CDeviceCommandContext::BeginScopedLabel(const char*) { } void CDeviceCommandContext::EndScopedLabel() { } void CDeviceCommandContext::Flush() { } void CDeviceCommandContext::BlitFramebuffer(IFramebuffer*, IFramebuffer*) { } void CDeviceCommandContext::ClearFramebuffer() { } void CDeviceCommandContext::ClearFramebuffer(const bool, const bool, const bool) { } void CDeviceCommandContext::SetFramebuffer(IFramebuffer*) { } void CDeviceCommandContext::ReadbackFramebufferSync( const uint32_t, const uint32_t, const uint32_t, const uint32_t, void*) { } void CDeviceCommandContext::SetScissors(const uint32_t, const Rect*) { } void CDeviceCommandContext::SetViewports(const uint32_t, const Rect*) { } void CDeviceCommandContext::SetVertexAttributeFormat( const VertexAttributeStream, const Format, - const uint32_t, const uint32_t, const uint32_t) + const uint32_t, const uint32_t, const VertexAttributeRate, const uint32_t) { } void CDeviceCommandContext::SetVertexBuffer(const uint32_t, IBuffer*) { } -void CDeviceCommandContext::SetVertexBufferData(const uint32_t, const void*) +void CDeviceCommandContext::SetVertexBufferData( + const uint32_t, const void*, const uint32_t) { } void CDeviceCommandContext::SetIndexBuffer(IBuffer*) { } -void CDeviceCommandContext::SetIndexBufferData(const void*) +void CDeviceCommandContext::SetIndexBufferData(const void*, const uint32_t) { } void CDeviceCommandContext::BeginPass() { } void CDeviceCommandContext::EndPass() { } void CDeviceCommandContext::Draw(const uint32_t, const uint32_t) { } void CDeviceCommandContext::DrawIndexed(const uint32_t, const uint32_t, const int32_t) { } +void CDeviceCommandContext::DrawInstanced( + const uint32_t, const uint32_t, const uint32_t, const uint32_t) +{ +} + +void CDeviceCommandContext::DrawIndexedInstanced( + const uint32_t, const uint32_t, const uint32_t, const uint32_t, const int32_t) +{ +} + void CDeviceCommandContext::DrawIndexedInRange( const uint32_t, const uint32_t, const uint32_t, const uint32_t) { } void CDeviceCommandContext::SetTexture(const int32_t, ITexture*) { } void CDeviceCommandContext::SetUniform(const int32_t, const float) { } void CDeviceCommandContext::SetUniform(const int32_t, const float, const float) { } void CDeviceCommandContext::SetUniform( const int32_t, const float, const float, const float) { } void CDeviceCommandContext::SetUniform( const int32_t, const float, const float, const float, const float) { } void CDeviceCommandContext::SetUniform(const int32_t, PS::span) { } } // namespace Dummy } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/gl/Device.cpp =================================================================== --- ps/trunk/source/renderer/backend/gl/Device.cpp (revision 26900) +++ ps/trunk/source/renderer/backend/gl/Device.cpp (revision 26901) @@ -1,909 +1,919 @@ /* 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 // TODO: Support OpenGL platforms which don't use GLX as well. #if defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES #include #include #endif 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) && !CONFIG2_GLES ogl_Init(SDL_GL_GetProcAddress, GetX11Display(device->m_Window)); #else ogl_Init(SDL_GL_GetProcAddress); #endif } else { #if OS_WIN ogl_Init(SDL_GL_GetProcAddress, wutil_GetAppHDC()); #elif defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES ogl_Init(SDL_GL_GetProcAddress, XOpenDisplay(NULL)); #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 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, format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount); } std::unique_ptr CDevice::CreateTexture2D(const char* name, 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, 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::Present() { 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; } } // namespace GL } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/gl/DeviceCommandContext.h =================================================================== --- ps/trunk/source/renderer/backend/gl/DeviceCommandContext.h (revision 26900) +++ ps/trunk/source/renderer/backend/gl/DeviceCommandContext.h (revision 26901) @@ -1,225 +1,235 @@ /* 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_GL_DEVICECOMMANDCONTEXT #define INCLUDED_RENDERER_GL_DEVICECOMMANDCONTEXT #include "lib/ogl.h" #include "ps/containers/Span.h" #include "renderer/backend/Format.h" #include "renderer/backend/gl/Buffer.h" #include "renderer/backend/IDeviceCommandContext.h" #include "renderer/backend/PipelineState.h" #include #include #include #include #include #include namespace Renderer { namespace Backend { namespace GL { class CDevice; class CFramebuffer; class CShaderProgram; class CTexture; class CDeviceCommandContext final : public IDeviceCommandContext { public: ~CDeviceCommandContext(); IDevice* GetDevice() override; void SetGraphicsPipelineState(const GraphicsPipelineStateDesc& pipelineStateDesc) override; void BlitFramebuffer(IFramebuffer* destinationFramebuffer, IFramebuffer* sourceFramebuffer) override; void ClearFramebuffer() override; void ClearFramebuffer(const bool color, const bool depth, const bool stencil) override; void SetFramebuffer(IFramebuffer* framebuffer) override; void ReadbackFramebufferSync( const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height, void* data) override; void UploadTexture(ITexture* texture, const Format dataFormat, const void* data, const size_t dataSize, const uint32_t level = 0, const uint32_t layer = 0) override; void UploadTextureRegion(ITexture* texture, const Format dataFormat, const void* data, const size_t dataSize, const uint32_t xOffset, const uint32_t yOffset, const uint32_t width, const uint32_t height, const uint32_t level = 0, const uint32_t layer = 0) override; using UploadBufferFunction = std::function; void UploadBuffer(IBuffer* buffer, const void* data, const uint32_t dataSize) override; void UploadBuffer(IBuffer* buffer, const UploadBufferFunction& uploadFunction) override; void UploadBufferRegion( IBuffer* buffer, const void* data, const uint32_t dataOffset, const uint32_t dataSize) override; void UploadBufferRegion( IBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize, const UploadBufferFunction& uploadFunction) override; void SetScissors(const uint32_t scissorCount, const Rect* scissors) override; void SetViewports(const uint32_t viewportCount, const Rect* viewports) override; void SetVertexAttributeFormat( const VertexAttributeStream stream, const Format format, const uint32_t offset, const uint32_t stride, + const VertexAttributeRate rate, const uint32_t bindingSlot) override; void SetVertexBuffer(const uint32_t bindingSlot, IBuffer* buffer) override; - void SetVertexBufferData(const uint32_t bindingSlot, const void* data) override; + void SetVertexBufferData( + const uint32_t bindingSlot, const void* data, const uint32_t dataSize) override; void SetIndexBuffer(IBuffer* buffer) override; - void SetIndexBufferData(const void* data) override; + void SetIndexBufferData(const void* data, const uint32_t dataSize) override; void BeginPass() override; void EndPass() override; void Draw(const uint32_t firstVertex, const uint32_t vertexCount) override; void DrawIndexed( const uint32_t firstIndex, const uint32_t indexCount, const int32_t vertexOffset) override; + void DrawInstanced( + const uint32_t firstVertex, const uint32_t vertexCount, + const uint32_t firstInstance, const uint32_t instanceCount) override; + void DrawIndexedInstanced( + const uint32_t firstIndex, const uint32_t indexCount, + const uint32_t firstInstance, const uint32_t instanceCount, + const int32_t vertexOffset) override; void DrawIndexedInRange( const uint32_t firstIndex, const uint32_t indexCount, const uint32_t start, const uint32_t end) override; void SetTexture(const int32_t bindingSlot, ITexture* texture) override; void SetUniform( const int32_t bindingSlot, const float value) override; void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY) override; void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ) override; void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ, const float valueW) override; void SetUniform( const int32_t bindingSlot, PS::span values) override; void BeginScopedLabel(const char* name) override; void EndScopedLabel() override; void Flush() override; // We need to know when to invalidate our texture bind cache. void OnTextureDestroy(CTexture* texture); private: friend class CDevice; friend class CTexture; static std::unique_ptr Create(CDevice* device); CDeviceCommandContext(CDevice* device); void ResetStates(); void SetGraphicsPipelineStateImpl( const GraphicsPipelineStateDesc& pipelineStateDesc, const bool force); void BindTexture(const uint32_t unit, const GLenum target, const GLuint handle); void BindBuffer(const IBuffer::Type type, CBuffer* buffer); CDevice* m_Device = nullptr; GraphicsPipelineStateDesc m_GraphicsPipelineStateDesc{}; CFramebuffer* m_Framebuffer = nullptr; CShaderProgram* m_ShaderProgram = nullptr; uint32_t m_ScissorCount = 0; // GL2.1 doesn't support more than 1 scissor. std::array m_Scissors; uint32_t m_ScopedLabelDepth = 0; CBuffer* m_VertexBuffer = nullptr; CBuffer* m_IndexBuffer = nullptr; const void* m_IndexBufferData = nullptr; bool m_InsidePass = false; uint32_t m_ActiveTextureUnit = 0; struct BindUnit { GLenum target; GLuint handle; }; std::array m_BoundTextures; class ScopedBind { public: ScopedBind(CDeviceCommandContext* deviceCommandContext, const GLenum target, const GLuint handle); ~ScopedBind(); private: CDeviceCommandContext* m_DeviceCommandContext = nullptr; BindUnit m_OldBindUnit; uint32_t m_ActiveTextureUnit = 0; }; using BoundBuffer = std::pair; std::array m_BoundBuffers; class ScopedBufferBind { public: ScopedBufferBind( CDeviceCommandContext* deviceCommandContext, CBuffer* buffer); ~ScopedBufferBind(); private: CDeviceCommandContext* m_DeviceCommandContext = nullptr; size_t m_CacheIndex = 0; }; struct VertexAttributeFormat { Format format; uint32_t offset; uint32_t stride; + VertexAttributeRate rate; uint32_t bindingSlot; bool active; bool initialized; }; std::array< VertexAttributeFormat, static_cast(VertexAttributeStream::UV7) + 1> m_VertexAttributeFormat; }; } // namespace GL } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_GL_DEVICECOMMANDCONTEXT Index: ps/trunk/source/renderer/backend/dummy/Device.h =================================================================== --- ps/trunk/source/renderer/backend/dummy/Device.h (revision 26900) +++ ps/trunk/source/renderer/backend/dummy/Device.h (revision 26901) @@ -1,99 +1,99 @@ /* 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/IDevice.h" class CShaderDefines; namespace Renderer { namespace Backend { namespace Dummy { class CDeviceCommandContext; class CDevice : public IDevice { public: CDevice(); ~CDevice() override; 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 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 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 Present() override; bool IsTextureFormatSupported(const Format format) const override; const Capabilities& GetCapabilities() const override { return m_Capabilities; } -private: +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/dummy/DeviceCommandContext.h =================================================================== --- ps/trunk/source/renderer/backend/dummy/DeviceCommandContext.h (revision 26900) +++ ps/trunk/source/renderer/backend/dummy/DeviceCommandContext.h (revision 26901) @@ -1,143 +1,152 @@ /* 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_DUMMY_DEVICECOMMANDCONTEXT #define INCLUDED_RENDERER_DUMMY_DEVICECOMMANDCONTEXT #include "renderer/backend/Format.h" #include "renderer/backend/IDeviceCommandContext.h" #include "renderer/backend/PipelineState.h" #include namespace Renderer { namespace Backend { namespace Dummy { class CDevice; class CBuffer; class CFramebuffer; class CShaderProgram; class CTexture; class CDeviceCommandContext : public IDeviceCommandContext { public: ~CDeviceCommandContext(); IDevice* GetDevice() override; void SetGraphicsPipelineState(const GraphicsPipelineStateDesc& pipelineStateDesc) override; void BlitFramebuffer(IFramebuffer* destinationFramebuffer, IFramebuffer* sourceFramebuffer) override; void ClearFramebuffer() override; void ClearFramebuffer(const bool color, const bool depth, const bool stencil) override; void SetFramebuffer(IFramebuffer* framebuffer) override; void ReadbackFramebufferSync( const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height, void* data) override; void UploadTexture(ITexture* texture, const Format dataFormat, const void* data, const size_t dataSize, const uint32_t level = 0, const uint32_t layer = 0) override; void UploadTextureRegion(ITexture* texture, const Format dataFormat, const void* data, const size_t dataSize, const uint32_t xOffset, const uint32_t yOffset, const uint32_t width, const uint32_t height, const uint32_t level = 0, const uint32_t layer = 0) override; using UploadBufferFunction = std::function; void UploadBuffer(IBuffer* buffer, const void* data, const uint32_t dataSize) override; void UploadBuffer(IBuffer* buffer, const UploadBufferFunction& uploadFunction) override; void UploadBufferRegion( IBuffer* buffer, const void* data, const uint32_t dataOffset, const uint32_t dataSize) override; void UploadBufferRegion( IBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize, const UploadBufferFunction& uploadFunction) override; void SetScissors(const uint32_t scissorCount, const Rect* scissors) override; void SetViewports(const uint32_t viewportCount, const Rect* viewports) override; void SetVertexAttributeFormat( const VertexAttributeStream stream, const Format format, const uint32_t offset, const uint32_t stride, + const VertexAttributeRate rate, const uint32_t bindingSlot) override; void SetVertexBuffer(const uint32_t bindingSlot, IBuffer* buffer) override; - void SetVertexBufferData(const uint32_t bindingSlot, const void* data) override; + void SetVertexBufferData( + const uint32_t bindingSlot, const void* data, const uint32_t dataSize) override; void SetIndexBuffer(IBuffer* buffer) override; - void SetIndexBufferData(const void* data) override; + void SetIndexBufferData(const void* data, const uint32_t dataSize) override; void BeginPass() override; void EndPass() override; void Draw(const uint32_t firstVertex, const uint32_t vertexCount) override; void DrawIndexed( const uint32_t firstIndex, const uint32_t indexCount, const int32_t vertexOffset) override; + void DrawInstanced( + const uint32_t firstVertex, const uint32_t vertexCount, + const uint32_t firstInstance, const uint32_t instanceCount) override; + void DrawIndexedInstanced( + const uint32_t firstIndex, const uint32_t indexCount, + const uint32_t firstInstance, const uint32_t instanceCount, + const int32_t vertexOffset) override; void DrawIndexedInRange( const uint32_t firstIndex, const uint32_t indexCount, const uint32_t start, const uint32_t end) override; void SetTexture(const int32_t bindingSlot, ITexture* texture) override; void SetUniform( const int32_t bindingSlot, const float value) override; void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY) override; void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ) override; void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ, const float valueW) override; void SetUniform( const int32_t bindingSlot, PS::span values) override; void BeginScopedLabel(const char* name) override; void EndScopedLabel() override; void Flush() override; private: friend class CDevice; static std::unique_ptr Create(CDevice* device); CDeviceCommandContext(); CDevice* m_Device = nullptr; }; } // namespace Dummy } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_DUMMY_DEVICECOMMANDCONTEXT Index: ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp =================================================================== --- ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp (revision 26900) +++ ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp (revision 26901) @@ -1,1119 +1,1169 @@ /* 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 "DeviceCommandContext.h" #include "ps/CLogger.h" #include "renderer/backend/gl/Buffer.h" #include "renderer/backend/gl/Device.h" #include "renderer/backend/gl/Framebuffer.h" #include "renderer/backend/gl/Mapping.h" #include "renderer/backend/gl/ShaderProgram.h" #include "renderer/backend/gl/Texture.h" #include #include #include namespace Renderer { namespace Backend { namespace GL { namespace { bool operator==(const StencilOpState& lhs, const StencilOpState& rhs) { return lhs.failOp == rhs.failOp && lhs.passOp == rhs.passOp && lhs.depthFailOp == rhs.depthFailOp && lhs.compareOp == rhs.compareOp; } bool operator!=(const StencilOpState& lhs, const StencilOpState& rhs) { return !operator==(lhs, rhs); } bool operator==( const CDeviceCommandContext::Rect& lhs, const CDeviceCommandContext::Rect& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.width == rhs.width && lhs.height == rhs.height; } bool operator!=( const CDeviceCommandContext::Rect& lhs, const CDeviceCommandContext::Rect& rhs) { return !operator==(lhs, rhs); } void ApplyDepthMask(const bool depthWriteEnabled) { glDepthMask(depthWriteEnabled ? GL_TRUE : GL_FALSE); } void ApplyColorMask(const uint8_t colorWriteMask) { glColorMask( (colorWriteMask & ColorWriteMask::RED) != 0 ? GL_TRUE : GL_FALSE, (colorWriteMask & ColorWriteMask::GREEN) != 0 ? GL_TRUE : GL_FALSE, (colorWriteMask & ColorWriteMask::BLUE) != 0 ? GL_TRUE : GL_FALSE, (colorWriteMask & ColorWriteMask::ALPHA) != 0 ? GL_TRUE : GL_FALSE); } void ApplyStencilMask(const uint32_t stencilWriteMask) { glStencilMask(stencilWriteMask); } GLenum BufferTypeToGLTarget(const CBuffer::Type type) { GLenum target = GL_ARRAY_BUFFER; switch (type) { case CBuffer::Type::VERTEX: target = GL_ARRAY_BUFFER; break; case CBuffer::Type::INDEX: target = GL_ELEMENT_ARRAY_BUFFER; break; }; return target; } #if !CONFIG2_GLES bool IsDepthTexture(const Format format) { return format == Format::D16 || format == Format::D24 || format == Format::D32 || format == Format::D24_S8; } #endif // !CONFIG2_GLES void UploadDynamicBufferRegionImpl( const GLenum target, const uint32_t bufferSize, const uint32_t dataOffset, const uint32_t dataSize, const CDeviceCommandContext::UploadBufferFunction& uploadFunction) { ENSURE(dataOffset < dataSize); // Tell the driver that it can reallocate the whole VBO glBufferDataARB(target, bufferSize, nullptr, GL_DYNAMIC_DRAW); ogl_WarnIfError(); while (true) { // (In theory, glMapBufferRange with GL_MAP_INVALIDATE_BUFFER_BIT could be used // here instead of glBufferData(..., NULL, ...) plus glMapBuffer(), but with // current Intel Windows GPU drivers (as of 2015-01) it's much faster if you do // the explicit glBufferData.) void* mappedData = glMapBufferARB(target, GL_WRITE_ONLY); if (mappedData == nullptr) { // This shouldn't happen unless we run out of virtual address space LOGERROR("glMapBuffer failed"); break; } uploadFunction(static_cast(mappedData) + dataOffset); if (glUnmapBufferARB(target) == GL_TRUE) break; // Unmap might fail on e.g. resolution switches, so just try again // and hope it will eventually succeed LOGMESSAGE("glUnmapBuffer failed, trying again...\n"); } } } // anonymous namespace // static std::unique_ptr CDeviceCommandContext::Create(CDevice* device) { std::unique_ptr deviceCommandContext(new CDeviceCommandContext(device)); deviceCommandContext->m_Framebuffer = static_cast(device->GetCurrentBackbuffer()); deviceCommandContext->ResetStates(); return deviceCommandContext; } CDeviceCommandContext::CDeviceCommandContext(CDevice* device) : m_Device(device) { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0); for (BindUnit& unit : m_BoundTextures) { unit.target = GL_TEXTURE_2D; unit.handle = 0; } for (size_t index = 0; index < m_VertexAttributeFormat.size(); ++index) { m_VertexAttributeFormat[index].active = false; m_VertexAttributeFormat[index].initialized = false; m_VertexAttributeFormat[index].bindingSlot = 0; } for (size_t index = 0; index < m_BoundBuffers.size(); ++index) { const CBuffer::Type type = static_cast(index); const GLenum target = BufferTypeToGLTarget(type); const GLuint handle = 0; m_BoundBuffers[index].first = target; m_BoundBuffers[index].second = handle; } } CDeviceCommandContext::~CDeviceCommandContext() = default; IDevice* CDeviceCommandContext::GetDevice() { return m_Device; } void CDeviceCommandContext::SetGraphicsPipelineState( const GraphicsPipelineStateDesc& pipelineStateDesc) { SetGraphicsPipelineStateImpl(pipelineStateDesc, false); } void CDeviceCommandContext::UploadTexture( ITexture* texture, const Format format, const void* data, const size_t dataSize, const uint32_t level, const uint32_t layer) { UploadTextureRegion(texture, format, data, dataSize, 0, 0, std::max(1u, texture->GetWidth() >> level), std::max(1u, texture->GetHeight() >> level), level, layer); } void CDeviceCommandContext::UploadTextureRegion( ITexture* destinationTexture, const Format dataFormat, const void* data, const size_t dataSize, const uint32_t xOffset, const uint32_t yOffset, const uint32_t width, const uint32_t height, const uint32_t level, const uint32_t layer) { ENSURE(destinationTexture); CTexture* texture = destinationTexture->As(); ENSURE(width > 0 && height > 0); if (texture->GetType() == CTexture::Type::TEXTURE_2D) { ENSURE(layer == 0); if (texture->GetFormat() == Format::R8G8B8A8_UNORM || texture->GetFormat() == Format::R8G8B8_UNORM || texture->GetFormat() == Format::A8_UNORM) { ENSURE(texture->GetFormat() == dataFormat); size_t bytesPerPixel = 4; GLenum pixelFormat = GL_RGBA; switch (dataFormat) { case Format::R8G8B8A8_UNORM: break; case Format::R8G8B8_UNORM: pixelFormat = GL_RGB; bytesPerPixel = 3; break; case Format::A8_UNORM: pixelFormat = GL_ALPHA; bytesPerPixel = 1; break; case Format::L8_UNORM: pixelFormat = GL_LUMINANCE; bytesPerPixel = 1; break; default: debug_warn("Unexpected format."); break; } ENSURE(dataSize == width * height * bytesPerPixel); ScopedBind scopedBind(this, GL_TEXTURE_2D, texture->GetHandle()); glTexSubImage2D(GL_TEXTURE_2D, level, xOffset, yOffset, width, height, pixelFormat, GL_UNSIGNED_BYTE, data); ogl_WarnIfError(); } else if ( texture->GetFormat() == Format::BC1_RGB_UNORM || texture->GetFormat() == Format::BC1_RGBA_UNORM || texture->GetFormat() == Format::BC2_UNORM || texture->GetFormat() == Format::BC3_UNORM) { ENSURE(xOffset == 0 && yOffset == 0); ENSURE(texture->GetFormat() == dataFormat); // TODO: add data size check. GLenum internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; switch (texture->GetFormat()) { case Format::BC1_RGBA_UNORM: internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; case Format::BC2_UNORM: internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; case Format::BC3_UNORM: internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; default: break; } ScopedBind scopedBind(this, GL_TEXTURE_2D, texture->GetHandle()); glCompressedTexImage2DARB(GL_TEXTURE_2D, level, internalFormat, width, height, 0, dataSize, data); ogl_WarnIfError(); } else debug_warn("Unsupported format"); } else if (texture->GetType() == CTexture::Type::TEXTURE_CUBE) { if (texture->GetFormat() == Format::R8G8B8A8_UNORM) { ENSURE(texture->GetFormat() == dataFormat); ENSURE(level == 0 && layer < 6); ENSURE(xOffset == 0 && yOffset == 0 && texture->GetWidth() == width && texture->GetHeight() == height); const size_t bpp = 4; ENSURE(dataSize == width * height * bpp); // The order of layers should be the following: // front, back, top, bottom, right, left static const GLenum targets[6] = { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z }; ScopedBind scopedBind(this, GL_TEXTURE_CUBE_MAP, texture->GetHandle()); glTexImage2D(targets[layer], level, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); ogl_WarnIfError(); } else debug_warn("Unsupported format"); } else debug_warn("Unsupported type"); } void CDeviceCommandContext::UploadBuffer(IBuffer* buffer, const void* data, const uint32_t dataSize) { UploadBufferRegion(buffer, data, dataSize, 0); } void CDeviceCommandContext::UploadBuffer( IBuffer* buffer, const UploadBufferFunction& uploadFunction) { UploadBufferRegion(buffer, 0, buffer->GetSize(), uploadFunction); } void CDeviceCommandContext::UploadBufferRegion( IBuffer* buffer, const void* data, const uint32_t dataOffset, const uint32_t dataSize) { ENSURE(data); ENSURE(dataOffset + dataSize <= buffer->GetSize()); const GLenum target = BufferTypeToGLTarget(buffer->GetType()); ScopedBufferBind scopedBufferBind(this, buffer->As()); if (buffer->IsDynamic()) { UploadDynamicBufferRegionImpl(target, buffer->GetSize(), dataOffset, dataSize, [data, dataSize](u8* mappedData) { std::memcpy(mappedData, data, dataSize); }); } else { glBufferSubDataARB(target, dataOffset, dataSize, data); ogl_WarnIfError(); } } void CDeviceCommandContext::UploadBufferRegion( IBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize, const UploadBufferFunction& uploadFunction) { ENSURE(dataOffset + dataSize <= buffer->GetSize()); const GLenum target = BufferTypeToGLTarget(buffer->GetType()); ScopedBufferBind scopedBufferBind(this, buffer->As()); ENSURE(buffer->IsDynamic()); UploadDynamicBufferRegionImpl(target, buffer->GetSize(), dataOffset, dataSize, uploadFunction); } void CDeviceCommandContext::BeginScopedLabel(const char* name) { if (!m_Device->GetCapabilities().debugScopedLabels) return; ++m_ScopedLabelDepth; glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0x0AD, -1, name); } void CDeviceCommandContext::EndScopedLabel() { if (!m_Device->GetCapabilities().debugScopedLabels) return; ENSURE(m_ScopedLabelDepth > 0); --m_ScopedLabelDepth; glPopDebugGroup(); } void CDeviceCommandContext::BindTexture( const uint32_t unit, const GLenum target, const GLuint handle) { ENSURE(unit < m_BoundTextures.size()); #if CONFIG2_GLES ENSURE(target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP); #else ENSURE(target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP || target == GL_TEXTURE_2D_MULTISAMPLE); #endif if (m_ActiveTextureUnit != unit) { glActiveTexture(GL_TEXTURE0 + unit); m_ActiveTextureUnit = unit; } if (m_BoundTextures[unit].target == target && m_BoundTextures[unit].handle == handle) return; if (m_BoundTextures[unit].target != target && m_BoundTextures[unit].target && m_BoundTextures[unit].handle) glBindTexture(m_BoundTextures[unit].target, 0); if (m_BoundTextures[unit].handle != handle) glBindTexture(target, handle); ogl_WarnIfError(); m_BoundTextures[unit] = {target, handle}; } void CDeviceCommandContext::BindBuffer(const IBuffer::Type type, CBuffer* buffer) { ENSURE(!buffer || buffer->GetType() == type); if (type == IBuffer::Type::VERTEX) { if (m_VertexBuffer == buffer) return; m_VertexBuffer = buffer; } else if (type == IBuffer::Type::INDEX) { if (!buffer) m_IndexBuffer = nullptr; m_IndexBufferData = nullptr; } const GLenum target = BufferTypeToGLTarget(type); const GLuint handle = buffer ? buffer->GetHandle() : 0; glBindBufferARB(target, handle); ogl_WarnIfError(); const size_t cacheIndex = static_cast(type); ENSURE(cacheIndex < m_BoundBuffers.size()); m_BoundBuffers[cacheIndex].second = handle; } void CDeviceCommandContext::OnTextureDestroy(CTexture* texture) { ENSURE(texture); for (size_t index = 0; index < m_BoundTextures.size(); ++index) if (m_BoundTextures[index].handle == texture->GetHandle()) BindTexture(index, GL_TEXTURE_2D, 0); } void CDeviceCommandContext::Flush() { ENSURE(m_ScopedLabelDepth == 0); GPU_SCOPED_LABEL(this, "CDeviceCommandContext::Flush"); ResetStates(); m_IndexBuffer = nullptr; m_IndexBufferData = nullptr; for (size_t unit = 0; unit < m_BoundTextures.size(); ++unit) { if (m_BoundTextures[unit].handle) BindTexture(unit, GL_TEXTURE_2D, 0); } BindBuffer(CBuffer::Type::INDEX, nullptr); BindBuffer(CBuffer::Type::VERTEX, nullptr); } void CDeviceCommandContext::ResetStates() { SetGraphicsPipelineStateImpl(MakeDefaultGraphicsPipelineStateDesc(), true); SetScissors(0, nullptr); SetFramebuffer(m_Device->GetCurrentBackbuffer()); } void CDeviceCommandContext::SetGraphicsPipelineStateImpl( const GraphicsPipelineStateDesc& pipelineStateDesc, const bool force) { ENSURE(!m_InsidePass); if (m_GraphicsPipelineStateDesc.shaderProgram != pipelineStateDesc.shaderProgram) { CShaderProgram* currentShaderProgram = nullptr; if (m_GraphicsPipelineStateDesc.shaderProgram) { currentShaderProgram = static_cast(m_GraphicsPipelineStateDesc.shaderProgram); } CShaderProgram* nextShaderProgram = nullptr; if (pipelineStateDesc.shaderProgram) { nextShaderProgram = static_cast(pipelineStateDesc.shaderProgram); for (size_t index = 0; index < m_VertexAttributeFormat.size(); ++index) { const VertexAttributeStream stream = static_cast(index); m_VertexAttributeFormat[index].active = nextShaderProgram->IsStreamActive(stream); m_VertexAttributeFormat[index].initialized = false; m_VertexAttributeFormat[index].bindingSlot = std::numeric_limits::max(); } } if (nextShaderProgram) nextShaderProgram->Bind(currentShaderProgram); else if (currentShaderProgram) currentShaderProgram->Unbind(); m_ShaderProgram = nextShaderProgram; } const DepthStencilStateDesc& currentDepthStencilStateDesc = m_GraphicsPipelineStateDesc.depthStencilState; const DepthStencilStateDesc& nextDepthStencilStateDesc = pipelineStateDesc.depthStencilState; if (force || currentDepthStencilStateDesc.depthTestEnabled != nextDepthStencilStateDesc.depthTestEnabled) { if (nextDepthStencilStateDesc.depthTestEnabled) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); } if (force || currentDepthStencilStateDesc.depthCompareOp != nextDepthStencilStateDesc.depthCompareOp) { glDepthFunc(Mapping::FromCompareOp(nextDepthStencilStateDesc.depthCompareOp)); } if (force || currentDepthStencilStateDesc.depthWriteEnabled != nextDepthStencilStateDesc.depthWriteEnabled) { ApplyDepthMask(nextDepthStencilStateDesc.depthWriteEnabled); } if (force || currentDepthStencilStateDesc.stencilTestEnabled != nextDepthStencilStateDesc.stencilTestEnabled) { if (nextDepthStencilStateDesc.stencilTestEnabled) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST); } if (force || currentDepthStencilStateDesc.stencilFrontFace != nextDepthStencilStateDesc.stencilFrontFace || currentDepthStencilStateDesc.stencilBackFace != nextDepthStencilStateDesc.stencilBackFace) { if (nextDepthStencilStateDesc.stencilFrontFace == nextDepthStencilStateDesc.stencilBackFace) { glStencilOp( Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.failOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.depthFailOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.passOp)); } else { if (force || currentDepthStencilStateDesc.stencilFrontFace != nextDepthStencilStateDesc.stencilFrontFace) { glStencilOpSeparate( GL_FRONT, Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.failOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.depthFailOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.passOp)); } if (force || currentDepthStencilStateDesc.stencilBackFace != nextDepthStencilStateDesc.stencilBackFace) { glStencilOpSeparate( GL_BACK, Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.failOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.depthFailOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.passOp)); } } } if (force || currentDepthStencilStateDesc.stencilWriteMask != nextDepthStencilStateDesc.stencilWriteMask) { ApplyStencilMask(nextDepthStencilStateDesc.stencilWriteMask); } if (force || currentDepthStencilStateDesc.stencilReference != nextDepthStencilStateDesc.stencilReference || currentDepthStencilStateDesc.stencilReadMask != nextDepthStencilStateDesc.stencilReadMask || currentDepthStencilStateDesc.stencilFrontFace.compareOp != nextDepthStencilStateDesc.stencilFrontFace.compareOp || currentDepthStencilStateDesc.stencilBackFace.compareOp != nextDepthStencilStateDesc.stencilBackFace.compareOp) { if (nextDepthStencilStateDesc.stencilFrontFace.compareOp == nextDepthStencilStateDesc.stencilBackFace.compareOp) { glStencilFunc( Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilFrontFace.compareOp), nextDepthStencilStateDesc.stencilReference, nextDepthStencilStateDesc.stencilReadMask); } else { glStencilFuncSeparate(GL_FRONT, Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilFrontFace.compareOp), nextDepthStencilStateDesc.stencilReference, nextDepthStencilStateDesc.stencilReadMask); glStencilFuncSeparate(GL_BACK, Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilBackFace.compareOp), nextDepthStencilStateDesc.stencilReference, nextDepthStencilStateDesc.stencilReadMask); } } const BlendStateDesc& currentBlendStateDesc = m_GraphicsPipelineStateDesc.blendState; const BlendStateDesc& nextBlendStateDesc = pipelineStateDesc.blendState; if (force || currentBlendStateDesc.enabled != nextBlendStateDesc.enabled) { if (nextBlendStateDesc.enabled) glEnable(GL_BLEND); else glDisable(GL_BLEND); } if (force || currentBlendStateDesc.srcColorBlendFactor != nextBlendStateDesc.srcColorBlendFactor || currentBlendStateDesc.srcAlphaBlendFactor != nextBlendStateDesc.srcAlphaBlendFactor || currentBlendStateDesc.dstColorBlendFactor != nextBlendStateDesc.dstColorBlendFactor || currentBlendStateDesc.dstAlphaBlendFactor != nextBlendStateDesc.dstAlphaBlendFactor) { if (nextBlendStateDesc.srcColorBlendFactor == nextBlendStateDesc.srcAlphaBlendFactor && nextBlendStateDesc.dstColorBlendFactor == nextBlendStateDesc.dstAlphaBlendFactor) { glBlendFunc( Mapping::FromBlendFactor(nextBlendStateDesc.srcColorBlendFactor), Mapping::FromBlendFactor(nextBlendStateDesc.dstColorBlendFactor)); } else { glBlendFuncSeparate( Mapping::FromBlendFactor(nextBlendStateDesc.srcColorBlendFactor), Mapping::FromBlendFactor(nextBlendStateDesc.dstColorBlendFactor), Mapping::FromBlendFactor(nextBlendStateDesc.srcAlphaBlendFactor), Mapping::FromBlendFactor(nextBlendStateDesc.dstAlphaBlendFactor)); } } if (force || currentBlendStateDesc.colorBlendOp != nextBlendStateDesc.colorBlendOp || currentBlendStateDesc.alphaBlendOp != nextBlendStateDesc.alphaBlendOp) { if (nextBlendStateDesc.colorBlendOp == nextBlendStateDesc.alphaBlendOp) { glBlendEquation(Mapping::FromBlendOp(nextBlendStateDesc.colorBlendOp)); } else { glBlendEquationSeparate( Mapping::FromBlendOp(nextBlendStateDesc.colorBlendOp), Mapping::FromBlendOp(nextBlendStateDesc.alphaBlendOp)); } } if (force || currentBlendStateDesc.constant != nextBlendStateDesc.constant) { glBlendColor( nextBlendStateDesc.constant.r, nextBlendStateDesc.constant.g, nextBlendStateDesc.constant.b, nextBlendStateDesc.constant.a); } if (force || currentBlendStateDesc.colorWriteMask != nextBlendStateDesc.colorWriteMask) { ApplyColorMask(nextBlendStateDesc.colorWriteMask); } const RasterizationStateDesc& currentRasterizationStateDesc = m_GraphicsPipelineStateDesc.rasterizationState; const RasterizationStateDesc& nextRasterizationStateDesc = pipelineStateDesc.rasterizationState; if (force || currentRasterizationStateDesc.polygonMode != nextRasterizationStateDesc.polygonMode) { #if !CONFIG2_GLES glPolygonMode( GL_FRONT_AND_BACK, nextRasterizationStateDesc.polygonMode == PolygonMode::LINE ? GL_LINE : GL_FILL); #endif } if (force || currentRasterizationStateDesc.cullMode != nextRasterizationStateDesc.cullMode) { if (nextRasterizationStateDesc.cullMode == CullMode::NONE) { glDisable(GL_CULL_FACE); } else { if (force || currentRasterizationStateDesc.cullMode == CullMode::NONE) glEnable(GL_CULL_FACE); glCullFace(nextRasterizationStateDesc.cullMode == CullMode::FRONT ? GL_FRONT : GL_BACK); } } if (force || currentRasterizationStateDesc.frontFace != nextRasterizationStateDesc.frontFace) { if (nextRasterizationStateDesc.frontFace == FrontFace::CLOCKWISE) glFrontFace(GL_CW); else glFrontFace(GL_CCW); } #if !CONFIG2_GLES if (force || currentRasterizationStateDesc.depthBiasEnabled != nextRasterizationStateDesc.depthBiasEnabled) { if (nextRasterizationStateDesc.depthBiasEnabled) glEnable(GL_POLYGON_OFFSET_FILL); else glDisable(GL_POLYGON_OFFSET_FILL); } if (force || currentRasterizationStateDesc.depthBiasConstantFactor != nextRasterizationStateDesc.depthBiasConstantFactor || currentRasterizationStateDesc.depthBiasSlopeFactor != nextRasterizationStateDesc.depthBiasSlopeFactor) { glPolygonOffset( nextRasterizationStateDesc.depthBiasSlopeFactor, nextRasterizationStateDesc.depthBiasConstantFactor); } #endif ogl_WarnIfError(); m_GraphicsPipelineStateDesc = pipelineStateDesc; } void CDeviceCommandContext::BlitFramebuffer( IFramebuffer* dstFramebuffer, IFramebuffer* srcFramebuffer) { CFramebuffer* destinationFramebuffer = dstFramebuffer->As(); CFramebuffer* sourceFramebuffer = srcFramebuffer->As(); #if CONFIG2_GLES UNUSED2(destinationFramebuffer); UNUSED2(sourceFramebuffer); debug_warn("CDeviceCommandContext::BlitFramebuffer is not implemented for GLES"); #else // Source framebuffer should not be backbuffer. ENSURE(sourceFramebuffer->GetHandle() != 0); ENSURE(destinationFramebuffer != sourceFramebuffer); glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, sourceFramebuffer->GetHandle()); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, destinationFramebuffer->GetHandle()); // TODO: add more check for internal formats. And currently we don't support // scaling inside blit. glBlitFramebufferEXT( 0, 0, sourceFramebuffer->GetWidth(), sourceFramebuffer->GetHeight(), 0, 0, sourceFramebuffer->GetWidth(), sourceFramebuffer->GetHeight(), (sourceFramebuffer->GetAttachmentMask() & destinationFramebuffer->GetAttachmentMask()), GL_NEAREST); ogl_WarnIfError(); #endif } void CDeviceCommandContext::ClearFramebuffer() { ClearFramebuffer(true, true, true); } void CDeviceCommandContext::ClearFramebuffer(const bool color, const bool depth, const bool stencil) { const bool needsColor = color && (m_Framebuffer->GetAttachmentMask() & GL_COLOR_BUFFER_BIT) != 0; const bool needsDepth = depth && (m_Framebuffer->GetAttachmentMask() & GL_DEPTH_BUFFER_BIT) != 0; const bool needsStencil = stencil && (m_Framebuffer->GetAttachmentMask() & GL_STENCIL_BUFFER_BIT) != 0; GLbitfield mask = 0; if (needsColor) { ApplyColorMask(ColorWriteMask::RED | ColorWriteMask::GREEN | ColorWriteMask::BLUE | ColorWriteMask::ALPHA); glClearColor( m_Framebuffer->GetClearColor().r, m_Framebuffer->GetClearColor().g, m_Framebuffer->GetClearColor().b, m_Framebuffer->GetClearColor().a); mask |= GL_COLOR_BUFFER_BIT; } if (needsDepth) { ApplyDepthMask(true); mask |= GL_DEPTH_BUFFER_BIT; } if (needsStencil) { ApplyStencilMask(std::numeric_limits::max()); mask |= GL_STENCIL_BUFFER_BIT; } glClear(mask); ogl_WarnIfError(); if (needsColor) ApplyColorMask(m_GraphicsPipelineStateDesc.blendState.colorWriteMask); if (needsDepth) ApplyDepthMask(m_GraphicsPipelineStateDesc.depthStencilState.depthWriteEnabled); if (needsStencil) ApplyStencilMask(m_GraphicsPipelineStateDesc.depthStencilState.stencilWriteMask); } void CDeviceCommandContext::SetFramebuffer(IFramebuffer* framebuffer) { ENSURE(framebuffer); m_Framebuffer = framebuffer->As(); ENSURE(m_Framebuffer->GetHandle() == 0 || (m_Framebuffer->GetWidth() > 0 && m_Framebuffer->GetHeight() > 0)); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_Framebuffer->GetHandle()); ogl_WarnIfError(); } void CDeviceCommandContext::ReadbackFramebufferSync( const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height, void* data) { ENSURE(m_Framebuffer); glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, data); ogl_WarnIfError(); } void CDeviceCommandContext::SetScissors(const uint32_t scissorCount, const Rect* scissors) { ENSURE(scissorCount <= 1); if (scissorCount == 0) { if (m_ScissorCount != scissorCount) glDisable(GL_SCISSOR_TEST); } else { if (m_ScissorCount != scissorCount) glEnable(GL_SCISSOR_TEST); ENSURE(scissors); if (m_ScissorCount != scissorCount || m_Scissors[0] != scissors[0]) { m_Scissors[0] = scissors[0]; glScissor(m_Scissors[0].x, m_Scissors[0].y, m_Scissors[0].width, m_Scissors[0].height); } } ogl_WarnIfError(); m_ScissorCount = scissorCount; } void CDeviceCommandContext::SetViewports(const uint32_t viewportCount, const Rect* viewports) { ENSURE(viewportCount == 1); glViewport(viewports[0].x, viewports[0].y, viewports[0].width, viewports[0].height); ogl_WarnIfError(); } void CDeviceCommandContext::SetVertexAttributeFormat( - const VertexAttributeStream stream, - const Format format, - const uint32_t offset, - const uint32_t stride, - const uint32_t bindingSlot) + const VertexAttributeStream stream, + const Format format, + const uint32_t offset, + const uint32_t stride, + const VertexAttributeRate rate, + const uint32_t bindingSlot) { const uint32_t index = static_cast(stream); ENSURE(index < m_VertexAttributeFormat.size()); ENSURE(bindingSlot < m_VertexAttributeFormat.size()); if (!m_VertexAttributeFormat[index].active) return; m_VertexAttributeFormat[index].format = format; m_VertexAttributeFormat[index].offset = offset; m_VertexAttributeFormat[index].stride = stride; + m_VertexAttributeFormat[index].rate = rate; m_VertexAttributeFormat[index].bindingSlot = bindingSlot; m_VertexAttributeFormat[index].initialized = true; } void CDeviceCommandContext::SetVertexBuffer( const uint32_t bindingSlot, IBuffer* buffer) { ENSURE(buffer); ENSURE(buffer->GetType() == IBuffer::Type::VERTEX); ENSURE(m_ShaderProgram); BindBuffer(buffer->GetType(), buffer->As()); for (size_t index = 0; index < m_VertexAttributeFormat.size(); ++index) { if (!m_VertexAttributeFormat[index].active || m_VertexAttributeFormat[index].bindingSlot != bindingSlot) continue; ENSURE(m_VertexAttributeFormat[index].initialized); const VertexAttributeStream stream = static_cast(index); m_ShaderProgram->VertexAttribPointer(stream, m_VertexAttributeFormat[index].format, m_VertexAttributeFormat[index].offset, m_VertexAttributeFormat[index].stride, + m_VertexAttributeFormat[index].rate, nullptr); } } void CDeviceCommandContext::SetVertexBufferData( - const uint32_t bindingSlot, const void* data) + const uint32_t bindingSlot, const void* data, const uint32_t dataSize) { ENSURE(data); ENSURE(m_ShaderProgram); + ENSURE(dataSize > 0); BindBuffer(CBuffer::Type::VERTEX, nullptr); for (size_t index = 0; index < m_VertexAttributeFormat.size(); ++index) { if (!m_VertexAttributeFormat[index].active || m_VertexAttributeFormat[index].bindingSlot != bindingSlot) continue; ENSURE(m_VertexAttributeFormat[index].initialized); const VertexAttributeStream stream = static_cast(index); + // We don't know how many vertices will be used in a draw command, so we + // assume at least one vertex. + ENSURE(dataSize >= m_VertexAttributeFormat[index].offset + m_VertexAttributeFormat[index].stride); m_ShaderProgram->VertexAttribPointer(stream, m_VertexAttributeFormat[index].format, m_VertexAttributeFormat[index].offset, m_VertexAttributeFormat[index].stride, + m_VertexAttributeFormat[index].rate, data); } } void CDeviceCommandContext::SetIndexBuffer(IBuffer* buffer) { ENSURE(buffer->GetType() == CBuffer::Type::INDEX); m_IndexBuffer = buffer->As(); m_IndexBufferData = nullptr; BindBuffer(CBuffer::Type::INDEX, m_IndexBuffer); } -void CDeviceCommandContext::SetIndexBufferData(const void* data) +void CDeviceCommandContext::SetIndexBufferData(const void* data, const uint32_t dataSize) { + ENSURE(dataSize > 0); if (m_IndexBuffer) { BindBuffer(CBuffer::Type::INDEX, nullptr); m_IndexBuffer = nullptr; } m_IndexBufferData = data; } void CDeviceCommandContext::BeginPass() { ENSURE(!m_InsidePass); m_InsidePass = true; } void CDeviceCommandContext::EndPass() { ENSURE(m_InsidePass); m_InsidePass = false; } void CDeviceCommandContext::Draw( const uint32_t firstVertex, const uint32_t vertexCount) { ENSURE(m_ShaderProgram); ENSURE(m_InsidePass); // Some drivers apparently don't like count = 0 in glDrawArrays here, so skip // all drawing in that case. if (vertexCount == 0) return; m_ShaderProgram->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, firstVertex, vertexCount); ogl_WarnIfError(); } void CDeviceCommandContext::DrawIndexed( const uint32_t firstIndex, const uint32_t indexCount, const int32_t vertexOffset) { ENSURE(m_ShaderProgram); ENSURE(m_InsidePass); if (indexCount == 0) return; ENSURE(m_IndexBuffer || m_IndexBufferData); ENSURE(vertexOffset == 0); if (m_IndexBuffer) { ENSURE(sizeof(uint16_t) * (firstIndex + indexCount) <= m_IndexBuffer->GetSize()); } m_ShaderProgram->AssertPointersBound(); // Don't use glMultiDrawElements here since it doesn't have a significant // performance impact and it suffers from various driver bugs (e.g. it breaks // in Mesa 7.10 swrast with index VBOs). glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, static_cast((static_cast(m_IndexBufferData) + sizeof(uint16_t) * firstIndex))); ogl_WarnIfError(); } +void CDeviceCommandContext::DrawInstanced( + const uint32_t firstVertex, const uint32_t vertexCount, + const uint32_t firstInstance, const uint32_t instanceCount) +{ + ENSURE(m_Device->GetCapabilities().instancing); + ENSURE(m_ShaderProgram); + ENSURE(m_InsidePass); + if (vertexCount == 0 || instanceCount == 0) + return; + ENSURE(firstInstance == 0); + m_ShaderProgram->AssertPointersBound(); + glDrawArraysInstancedARB(GL_TRIANGLES, firstVertex, vertexCount, instanceCount); + ogl_WarnIfError(); +} + +void CDeviceCommandContext::DrawIndexedInstanced( + const uint32_t firstIndex, const uint32_t indexCount, + const uint32_t firstInstance, const uint32_t instanceCount, + const int32_t vertexOffset) +{ + ENSURE(m_Device->GetCapabilities().instancing); + ENSURE(m_ShaderProgram); + ENSURE(m_InsidePass); + ENSURE(m_IndexBuffer || m_IndexBufferData); + if (indexCount == 0) + return; + ENSURE(firstInstance == 0 && vertexOffset == 0); + if (m_IndexBuffer) + { + ENSURE(sizeof(uint16_t) * (firstIndex + indexCount) <= m_IndexBuffer->GetSize()); + } + m_ShaderProgram->AssertPointersBound(); + // Don't use glMultiDrawElements here since it doesn't have a significant + // performance impact and it suffers from various driver bugs (e.g. it breaks + // in Mesa 7.10 swrast with index VBOs). + glDrawElementsInstancedARB(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, + static_cast((static_cast(m_IndexBufferData) + sizeof(uint16_t) * firstIndex)), + instanceCount); + ogl_WarnIfError(); +} + void CDeviceCommandContext::DrawIndexedInRange( const uint32_t firstIndex, const uint32_t indexCount, const uint32_t start, const uint32_t end) { ENSURE(m_ShaderProgram); ENSURE(m_InsidePass); if (indexCount == 0) return; ENSURE(m_IndexBuffer || m_IndexBufferData); const void* indices = static_cast((static_cast(m_IndexBufferData) + sizeof(uint16_t) * firstIndex)); m_ShaderProgram->AssertPointersBound(); // Draw with DrawRangeElements where available, since it might be more // efficient for slow hardware. #if CONFIG2_GLES UNUSED2(start); UNUSED2(end); glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, indices); #else glDrawRangeElementsEXT(GL_TRIANGLES, start, end, indexCount, GL_UNSIGNED_SHORT, indices); #endif ogl_WarnIfError(); } void CDeviceCommandContext::SetTexture(const int32_t bindingSlot, ITexture* texture) { ENSURE(m_ShaderProgram); ENSURE(texture); const CShaderProgram::TextureUnit textureUnit = m_ShaderProgram->GetTextureUnit(bindingSlot); if (!textureUnit.type) return; if (textureUnit.type != GL_SAMPLER_2D && #if !CONFIG2_GLES textureUnit.type != GL_SAMPLER_2D_SHADOW && #endif textureUnit.type != GL_SAMPLER_CUBE) { LOGERROR("CDeviceCommandContext::SetTexture: expected sampler at binding slot"); return; } #if !CONFIG2_GLES if (textureUnit.type == GL_SAMPLER_2D_SHADOW) { if (!IsDepthTexture(texture->GetFormat())) { LOGERROR("CDeviceCommandContext::SetTexture: Invalid texture type (expected depth texture)"); return; } } #endif ENSURE(textureUnit.unit >= 0); const uint32_t unit = textureUnit.unit; if (unit >= m_BoundTextures.size()) { LOGERROR("CDeviceCommandContext::SetTexture: Invalid texture unit (too big)"); return; } BindTexture(unit, textureUnit.target, texture->As()->GetHandle()); } void CDeviceCommandContext::SetUniform( const int32_t bindingSlot, const float value) { ENSURE(m_ShaderProgram); m_ShaderProgram->SetUniform(bindingSlot, value); } void CDeviceCommandContext::SetUniform( const int32_t bindingSlot, const float valueX, const float valueY) { ENSURE(m_ShaderProgram); m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY); } void CDeviceCommandContext::SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ) { ENSURE(m_ShaderProgram); m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY, valueZ); } void CDeviceCommandContext::SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ, const float valueW) { ENSURE(m_ShaderProgram); m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY, valueZ, valueW); } void CDeviceCommandContext::SetUniform( const int32_t bindingSlot, PS::span values) { ENSURE(m_ShaderProgram); m_ShaderProgram->SetUniform(bindingSlot, values); } CDeviceCommandContext::ScopedBind::ScopedBind( CDeviceCommandContext* deviceCommandContext, const GLenum target, const GLuint handle) : m_DeviceCommandContext(deviceCommandContext), m_OldBindUnit(deviceCommandContext->m_BoundTextures[deviceCommandContext->m_ActiveTextureUnit]), m_ActiveTextureUnit(deviceCommandContext->m_ActiveTextureUnit) { const uint32_t unit = m_DeviceCommandContext->m_BoundTextures.size() - 1; m_DeviceCommandContext->BindTexture(unit, target, handle); } CDeviceCommandContext::ScopedBind::~ScopedBind() { m_DeviceCommandContext->BindTexture( m_ActiveTextureUnit, m_OldBindUnit.target, m_OldBindUnit.handle); } CDeviceCommandContext::ScopedBufferBind::ScopedBufferBind( CDeviceCommandContext* deviceCommandContext, CBuffer* buffer) : m_DeviceCommandContext(deviceCommandContext) { ENSURE(buffer); m_CacheIndex = static_cast(buffer->GetType()); const GLenum target = BufferTypeToGLTarget(buffer->GetType()); const GLuint handle = buffer->GetHandle(); if (m_DeviceCommandContext->m_BoundBuffers[m_CacheIndex].first == target && m_DeviceCommandContext->m_BoundBuffers[m_CacheIndex].second == handle) { // Use an invalid index as a sign that we don't need to restore the // bound buffer. m_CacheIndex = m_DeviceCommandContext->m_BoundBuffers.size(); } else { glBindBufferARB(target, handle); } } CDeviceCommandContext::ScopedBufferBind::~ScopedBufferBind() { if (m_CacheIndex >= m_DeviceCommandContext->m_BoundBuffers.size()) return; glBindBufferARB( m_DeviceCommandContext->m_BoundBuffers[m_CacheIndex].first, m_DeviceCommandContext->m_BoundBuffers[m_CacheIndex].second); } } // namespace GL } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/gl/ShaderProgram.cpp =================================================================== --- ps/trunk/source/renderer/backend/gl/ShaderProgram.cpp (revision 26900) +++ ps/trunk/source/renderer/backend/gl/ShaderProgram.cpp (revision 26901) @@ -1,1492 +1,1495 @@ /* 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 "ShaderProgram.h" #include "graphics/Color.h" #include "graphics/PreprocessorWrapper.h" #include "graphics/ShaderManager.h" #include "graphics/TextureManager.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Profile.h" #include "ps/XML/Xeromyces.h" #include "renderer/backend/gl/Device.h" #include "renderer/backend/gl/DeviceCommandContext.h" #define USE_SHADER_XML_VALIDATION 1 #if USE_SHADER_XML_VALIDATION #include "ps/XML/RelaxNG.h" #include "ps/XML/XMLWriter.h" #endif #include #include #include namespace Renderer { namespace Backend { namespace GL { namespace { struct Binding { Binding(int a, int b) : first(a), second(b) { } Binding() : first(-1), second(-1) { } /** * Returns whether this uniform attribute is active in the shader. * If not then there's no point calling Uniform() to set its value. */ bool Active() const { return first != -1 || second != -1; } int first; int second; }; int GetStreamMask(const VertexAttributeStream stream) { return 1 << static_cast(stream); } GLint GLSizeFromFormat(const Format format) { GLint size = 1; if (format == Renderer::Backend::Format::R32_SFLOAT || format == Renderer::Backend::Format::R16_SINT) size = 1; else if ( format == Renderer::Backend::Format::R8G8_UNORM || format == Renderer::Backend::Format::R8G8_UINT || format == Renderer::Backend::Format::R16G16_SINT || format == Renderer::Backend::Format::R32G32_SFLOAT) size = 2; else if (format == Renderer::Backend::Format::R32G32B32_SFLOAT) size = 3; else if ( format == Renderer::Backend::Format::R32G32B32A32_SFLOAT || format == Renderer::Backend::Format::R8G8B8A8_UNORM || format == Renderer::Backend::Format::R8G8B8A8_UINT) size = 4; else debug_warn("Unsupported format."); return size; } GLenum GLTypeFromFormat(const Format format) { GLenum type = GL_FLOAT; if (format == Renderer::Backend::Format::R32_SFLOAT || format == Renderer::Backend::Format::R32G32_SFLOAT || format == Renderer::Backend::Format::R32G32B32_SFLOAT || format == Renderer::Backend::Format::R32G32B32A32_SFLOAT) type = GL_FLOAT; else if ( format == Renderer::Backend::Format::R16_SINT || format == Renderer::Backend::Format::R16G16_SINT) type = GL_SHORT; else if ( format == Renderer::Backend::Format::R8G8_UNORM || format == Renderer::Backend::Format::R8G8_UINT || format == Renderer::Backend::Format::R8G8B8A8_UNORM || format == Renderer::Backend::Format::R8G8B8A8_UINT) type = GL_UNSIGNED_BYTE; else debug_warn("Unsupported format."); return type; } GLboolean NormalizedFromFormat(const Format format) { switch (format) { case Format::R8G8_UNORM: FALLTHROUGH; case Format::R8G8B8_UNORM: FALLTHROUGH; case Format::R8G8B8A8_UNORM: FALLTHROUGH; case Format::R16_UNORM: FALLTHROUGH; case Format::R16G16_UNORM: return GL_TRUE; default: break; } return GL_FALSE; } int GetAttributeLocationFromStream( CDevice* device, const VertexAttributeStream stream) { // Old mapping makes sense only if we have an old/low-end hardware. Else we // need to use sequential numbering to fix #3054. We use presence of // compute shaders as a check that the hardware has universal CUs. if (device->GetCapabilities().computeShaders) { return static_cast(stream); } else { // Map known semantics onto the attribute locations documented by NVIDIA: // https://download.nvidia.com/developer/Papers/2005/OpenGL_2.0/NVIDIA_OpenGL_2.0_Support.pdf // https://developer.download.nvidia.com/opengl/glsl/glsl_release_notes.pdf switch (stream) { case VertexAttributeStream::POSITION: return 0; case VertexAttributeStream::NORMAL: return 2; case VertexAttributeStream::COLOR: return 3; case VertexAttributeStream::UV0: return 8; case VertexAttributeStream::UV1: return 9; case VertexAttributeStream::UV2: return 10; case VertexAttributeStream::UV3: return 11; case VertexAttributeStream::UV4: return 12; case VertexAttributeStream::UV5: return 13; case VertexAttributeStream::UV6: return 14; case VertexAttributeStream::UV7: return 15; } } debug_warn("Invalid attribute semantics"); return 0; } bool PreprocessShaderFile( bool arb, const CShaderDefines& defines, const VfsPath& path, CStr& source, std::vector& fileDependencies) { CVFSFile file; if (file.Load(g_VFS, path) != PSRETURN_OK) { LOGERROR("Failed to load shader file: '%s'", path.string8()); return false; } CPreprocessorWrapper preprocessor( [arb, &fileDependencies](const CStr& includePath, CStr& out) -> bool { const VfsPath includeFilePath( (arb ? L"shaders/arb/" : L"shaders/glsl/") + wstring_from_utf8(includePath)); // Add dependencies anyway to reload the shader when the file is // appeared. fileDependencies.push_back(includeFilePath); CVFSFile includeFile; if (includeFile.Load(g_VFS, includeFilePath) != PSRETURN_OK) { LOGERROR("Failed to load shader include file: '%s'", includeFilePath.string8()); return false; } out = includeFile.GetAsString(); return true; }); preprocessor.AddDefines(defines); #if CONFIG2_GLES if (!arb) { // GLES defines the macro "GL_ES" in its GLSL preprocessor, // but since we run our own preprocessor first, we need to explicitly // define it here preprocessor.AddDefine("GL_ES", "1"); } #endif source = preprocessor.Preprocess(file.GetAsString()); return true; } #if !CONFIG2_GLES std::tuple GetElementTypeAndCountFromString(const CStr& str) { #define CASE(MATCH_STRING, TYPE, ELEMENT_TYPE, ELEMENT_COUNT) \ if (str == MATCH_STRING) return {GL_ ## TYPE, GL_ ## ELEMENT_TYPE, ELEMENT_COUNT} CASE("float", FLOAT, FLOAT, 1); CASE("vec2", FLOAT_VEC2, FLOAT, 2); CASE("vec3", FLOAT_VEC3, FLOAT, 3); CASE("vec4", FLOAT_VEC4, FLOAT, 4); CASE("mat2", FLOAT_MAT2, FLOAT, 4); CASE("mat3", FLOAT_MAT3, FLOAT, 9); CASE("mat4", FLOAT_MAT4, FLOAT, 16); #if !CONFIG2_GLES // GL ES 2.0 doesn't support non-square matrices. CASE("mat2x3", FLOAT_MAT2x3, FLOAT, 6); CASE("mat2x4", FLOAT_MAT2x4, FLOAT, 8); CASE("mat3x2", FLOAT_MAT3x2, FLOAT, 6); CASE("mat3x4", FLOAT_MAT3x4, FLOAT, 12); CASE("mat4x2", FLOAT_MAT4x2, FLOAT, 8); CASE("mat4x3", FLOAT_MAT4x3, FLOAT, 12); #endif // A somewhat incomplete listing, missing "shadow" and "rect" versions // which are interpreted as 2D (NB: our shadowmaps may change // type based on user config). #if CONFIG2_GLES if (str == "sampler1D") debug_warn(L"sampler1D not implemented on GLES"); #else CASE("sampler1D", SAMPLER_1D, TEXTURE_1D, 1); #endif CASE("sampler2D", SAMPLER_2D, TEXTURE_2D, 1); #if CONFIG2_GLES if (str == "sampler2DShadow") debug_warn(L"sampler2DShadow not implemented on GLES"); if (str == "sampler3D") debug_warn(L"sampler3D not implemented on GLES"); #else CASE("sampler2DShadow", SAMPLER_2D_SHADOW, TEXTURE_2D, 1); CASE("sampler3D", SAMPLER_3D, TEXTURE_3D, 1); #endif CASE("samplerCube", SAMPLER_CUBE, TEXTURE_CUBE_MAP, 1); #undef CASE return {0, 0, 0}; } #endif // !CONFIG2_GLES } // anonymous namespace #if !CONFIG2_GLES class CShaderProgramARB final : public CShaderProgram { public: CShaderProgramARB( CDevice* device, const VfsPath& path, const VfsPath& vertexFilePath, const VfsPath& fragmentFilePath, const CShaderDefines& defines, const std::map>& vertexIndices, const std::map>& fragmentIndices, int streamflags) : CShaderProgram(streamflags), m_Device(device) { glGenProgramsARB(1, &m_VertexProgram); glGenProgramsARB(1, &m_FragmentProgram); std::vector newFileDependencies = {path, vertexFilePath, fragmentFilePath}; CStr vertexCode; if (!PreprocessShaderFile(true, defines, vertexFilePath, vertexCode, newFileDependencies)) return; CStr fragmentCode; if (!PreprocessShaderFile(true, defines, fragmentFilePath, fragmentCode, newFileDependencies)) return; m_FileDependencies = std::move(newFileDependencies); // TODO: replace by scoped bind. m_Device->GetActiveCommandContext()->SetGraphicsPipelineState( MakeDefaultGraphicsPipelineStateDesc()); if (!Compile(GL_VERTEX_PROGRAM_ARB, "vertex", m_VertexProgram, vertexFilePath, vertexCode)) return; if (!Compile(GL_FRAGMENT_PROGRAM_ARB, "fragment", m_FragmentProgram, fragmentFilePath, fragmentCode)) return; for (const auto& index : vertexIndices) { BindingSlot& bindingSlot = GetOrCreateBindingSlot(index.first); bindingSlot.vertexProgramLocation = index.second.second; const auto [type, elementType, elementCount] = GetElementTypeAndCountFromString(index.second.first); bindingSlot.type = type; bindingSlot.elementType = elementType; bindingSlot.elementCount = elementCount; } for (const auto& index : fragmentIndices) { BindingSlot& bindingSlot = GetOrCreateBindingSlot(index.first); bindingSlot.fragmentProgramLocation = index.second.second; const auto [type, elementType, elementCount] = GetElementTypeAndCountFromString(index.second.first); if (bindingSlot.type && type != bindingSlot.type) { LOGERROR("CShaderProgramARB: vertex and fragment program uniforms with the same name should have the same type."); } bindingSlot.type = type; bindingSlot.elementType = elementType; bindingSlot.elementCount = elementCount; } } ~CShaderProgramARB() override { glDeleteProgramsARB(1, &m_VertexProgram); glDeleteProgramsARB(1, &m_FragmentProgram); } bool Compile(GLuint target, const char* targetName, GLuint program, const VfsPath& file, const CStr& code) { ogl_WarnIfError(); glBindProgramARB(target, program); ogl_WarnIfError(); glProgramStringARB(target, GL_PROGRAM_FORMAT_ASCII_ARB, (GLsizei)code.length(), code.c_str()); if (ogl_SquelchError(GL_INVALID_OPERATION)) { GLint errPos = 0; glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errPos); int errLine = std::count(code.begin(), code.begin() + std::min((int)code.length(), errPos + 1), '\n') + 1; char* errStr = (char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB); LOGERROR("Failed to compile %s program '%s' (line %d):\n%s", targetName, file.string8(), errLine, errStr); return false; } glBindProgramARB(target, 0); ogl_WarnIfError(); return true; } void Bind(CShaderProgram* previousShaderProgram) override { if (previousShaderProgram) previousShaderProgram->Unbind(); glBindProgramARB(GL_VERTEX_PROGRAM_ARB, m_VertexProgram); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_FragmentProgram); BindClientStates(); } void Unbind() override { glBindProgramARB(GL_VERTEX_PROGRAM_ARB, 0); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); UnbindClientStates(); } IDevice* GetDevice() override { return m_Device; } int32_t GetBindingSlot(const CStrIntern name) const override { auto it = m_BindingSlotsMapping.find(name); return it == m_BindingSlotsMapping.end() ? -1 : it->second; } TextureUnit GetTextureUnit(const int32_t bindingSlot) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return { 0, 0, 0 }; TextureUnit textureUnit; textureUnit.type = m_BindingSlots[bindingSlot].type; textureUnit.target = m_BindingSlots[bindingSlot].elementType; textureUnit.unit = m_BindingSlots[bindingSlot].fragmentProgramLocation; return textureUnit; } void SetUniform( const int32_t bindingSlot, const float value) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].type != GL_FLOAT) { LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform type (expected float)"); return; } SetUniform(m_BindingSlots[bindingSlot], value, 0.0f, 0.0f, 0.0f); } void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC2) { LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform type (expected vec2)"); return; } SetUniform(m_BindingSlots[bindingSlot], valueX, valueY, 0.0f, 0.0f); } void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC3) { LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform type (expected vec3)"); return; } SetUniform(m_BindingSlots[bindingSlot], valueX, valueY, valueZ, 0.0f); } void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ, const float valueW) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC4) { LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform type (expected vec4)"); return; } SetUniform(m_BindingSlots[bindingSlot], valueX, valueY, valueZ, valueW); } void SetUniform( const int32_t bindingSlot, PS::span values) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].elementType != GL_FLOAT) { LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform element type (expected float)"); return; } if (m_BindingSlots[bindingSlot].elementCount > static_cast(values.size())) { LOGERROR( "CShaderProgramARB::SetUniform(): Invalid uniform element count (expected: %zu passed: %zu)", m_BindingSlots[bindingSlot].elementCount, values.size()); return; } const GLenum type = m_BindingSlots[bindingSlot].type; if (type == GL_FLOAT) SetUniform(m_BindingSlots[bindingSlot], values[0], 0.0f, 0.0f, 0.0f); else if (type == GL_FLOAT_VEC2) SetUniform(m_BindingSlots[bindingSlot], values[0], values[1], 0.0f, 0.0f); else if (type == GL_FLOAT_VEC3) SetUniform(m_BindingSlots[bindingSlot], values[0], values[1], values[2], 0.0f); else if (type == GL_FLOAT_VEC4) SetUniform(m_BindingSlots[bindingSlot], values[0], values[1], values[2], values[3]); else if (type == GL_FLOAT_MAT4) SetUniformMatrix(m_BindingSlots[bindingSlot], values); else LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform type (expected float, vec2, vec3, vec4, mat4)"); ogl_WarnIfError(); } std::vector GetFileDependencies() const override { return m_FileDependencies; } private: struct BindingSlot { CStrIntern name; int vertexProgramLocation; int fragmentProgramLocation; GLenum type; GLenum elementType; GLint elementCount; }; BindingSlot& GetOrCreateBindingSlot(const CStrIntern name) { auto it = m_BindingSlotsMapping.find(name); if (it == m_BindingSlotsMapping.end()) { m_BindingSlotsMapping[name] = m_BindingSlots.size(); BindingSlot bindingSlot{}; bindingSlot.name = name; bindingSlot.vertexProgramLocation = -1; bindingSlot.fragmentProgramLocation = -1; bindingSlot.elementType = 0; bindingSlot.elementCount = 0; m_BindingSlots.emplace_back(std::move(bindingSlot)); return m_BindingSlots.back(); } else return m_BindingSlots[it->second]; } void SetUniform( const BindingSlot& bindingSlot, const float v0, const float v1, const float v2, const float v3) { SetUniform(GL_VERTEX_PROGRAM_ARB, bindingSlot.vertexProgramLocation, v0, v1, v2, v3); SetUniform(GL_FRAGMENT_PROGRAM_ARB, bindingSlot.fragmentProgramLocation, v0, v1, v2, v3); } void SetUniform( const GLenum target, const int location, const float v0, const float v1, const float v2, const float v3) { if (location >= 0) { glProgramLocalParameter4fARB( target, static_cast(location), v0, v1, v2, v3); } } void SetUniformMatrix( const BindingSlot& bindingSlot, PS::span values) { const size_t mat4ElementCount = 16; ENSURE(values.size() == mat4ElementCount); SetUniformMatrix(GL_VERTEX_PROGRAM_ARB, bindingSlot.vertexProgramLocation, values); SetUniformMatrix(GL_FRAGMENT_PROGRAM_ARB, bindingSlot.fragmentProgramLocation, values); } void SetUniformMatrix( const GLenum target, const int location, PS::span values) { if (location >= 0) { glProgramLocalParameter4fARB( target, static_cast(location + 0), values[0], values[4], values[8], values[12]); glProgramLocalParameter4fARB( target, static_cast(location + 1), values[1], values[5], values[9], values[13]); glProgramLocalParameter4fARB( target, static_cast(location + 2), values[2], values[6], values[10], values[14]); glProgramLocalParameter4fARB( target, static_cast(location + 3), values[3], values[7], values[11], values[15]); } } CDevice* m_Device = nullptr; std::vector m_FileDependencies; GLuint m_VertexProgram; GLuint m_FragmentProgram; std::vector m_BindingSlots; std::unordered_map m_BindingSlotsMapping; }; #endif // !CONFIG2_GLES class CShaderProgramGLSL final : public CShaderProgram { public: CShaderProgramGLSL( CDevice* device, const CStr& name, const VfsPath& path, const VfsPath& vertexFilePath, const VfsPath& fragmentFilePath, const CShaderDefines& defines, const std::map& vertexAttribs, int streamflags) : CShaderProgram(streamflags), m_Device(device), m_Name(name), m_VertexAttribs(vertexAttribs) { for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) m_ActiveVertexAttributes.emplace_back(it->second); std::sort(m_ActiveVertexAttributes.begin(), m_ActiveVertexAttributes.end()); m_Program = 0; m_VertexShader = glCreateShader(GL_VERTEX_SHADER); m_FragmentShader = glCreateShader(GL_FRAGMENT_SHADER); m_FileDependencies = {path, vertexFilePath, fragmentFilePath}; #if !CONFIG2_GLES if (m_Device->GetCapabilities().debugLabels) { glObjectLabel(GL_SHADER, m_VertexShader, -1, vertexFilePath.string8().c_str()); glObjectLabel(GL_SHADER, m_FragmentShader, -1, fragmentFilePath.string8().c_str()); } #endif std::vector newFileDependencies = {path, vertexFilePath, fragmentFilePath}; CStr vertexCode; if (!PreprocessShaderFile(false, defines, vertexFilePath, vertexCode, newFileDependencies)) return; CStr fragmentCode; if (!PreprocessShaderFile(false, defines, fragmentFilePath, fragmentCode, newFileDependencies)) return; m_FileDependencies = std::move(newFileDependencies); if (vertexCode.empty()) { LOGERROR("Failed to preprocess vertex shader: '%s'", vertexFilePath.string8()); return; } if (fragmentCode.empty()) { LOGERROR("Failed to preprocess fragment shader: '%s'", fragmentFilePath.string8()); return; } #if CONFIG2_GLES // Ugly hack to replace desktop GLSL 1.10/1.20 with GLSL ES 1.00, // and also to set default float precision for fragment shaders vertexCode.Replace("#version 110\n", "#version 100\n"); vertexCode.Replace("#version 110\r\n", "#version 100\n"); vertexCode.Replace("#version 120\n", "#version 100\n"); vertexCode.Replace("#version 120\r\n", "#version 100\n"); fragmentCode.Replace("#version 110\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 110\r\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 120\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 120\r\n", "#version 100\nprecision mediump float;\n"); #endif // TODO: replace by scoped bind. m_Device->GetActiveCommandContext()->SetGraphicsPipelineState( MakeDefaultGraphicsPipelineStateDesc()); if (!Compile(m_VertexShader, vertexFilePath, vertexCode)) return; if (!Compile(m_FragmentShader, fragmentFilePath, fragmentCode)) return; if (!Link(vertexFilePath, fragmentFilePath)) return; } ~CShaderProgramGLSL() override { if (m_Program) glDeleteProgram(m_Program); glDeleteShader(m_VertexShader); glDeleteShader(m_FragmentShader); } bool Compile(GLuint shader, const VfsPath& file, const CStr& code) { const char* code_string = code.c_str(); GLint code_length = code.length(); glShaderSource(shader, 1, &code_string, &code_length); ogl_WarnIfError(); glCompileShader(shader); GLint ok = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); GLint length = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length); // Apparently sometimes GL_INFO_LOG_LENGTH is incorrectly reported as 0 // (http://code.google.com/p/android/issues/detail?id=9953) if (!ok && length == 0) length = 4096; if (length > 1) { char* infolog = new char[length]; glGetShaderInfoLog(shader, length, NULL, infolog); if (ok) LOGMESSAGE("Info when compiling shader '%s':\n%s", file.string8(), infolog); else LOGERROR("Failed to compile shader '%s':\n%s", file.string8(), infolog); delete[] infolog; } ogl_WarnIfError(); return ok; } bool Link(const VfsPath& vertexFilePath, const VfsPath& fragmentFilePath) { ENSURE(!m_Program); m_Program = glCreateProgram(); #if !CONFIG2_GLES if (m_Device->GetCapabilities().debugLabels) { glObjectLabel(GL_PROGRAM, m_Program, -1, m_Name.c_str()); } #endif glAttachShader(m_Program, m_VertexShader); ogl_WarnIfError(); glAttachShader(m_Program, m_FragmentShader); ogl_WarnIfError(); // Set up the attribute bindings explicitly, since apparently drivers // don't always pick the most efficient bindings automatically, // and also this lets us hardcode indexes into VertexPointer etc for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) glBindAttribLocation(m_Program, it->second, it->first.c_str()); glLinkProgram(m_Program); GLint ok = 0; glGetProgramiv(m_Program, GL_LINK_STATUS, &ok); GLint length = 0; glGetProgramiv(m_Program, GL_INFO_LOG_LENGTH, &length); if (!ok && length == 0) length = 4096; if (length > 1) { char* infolog = new char[length]; glGetProgramInfoLog(m_Program, length, NULL, infolog); if (ok) LOGMESSAGE("Info when linking program '%s'+'%s':\n%s", vertexFilePath.string8(), fragmentFilePath.string8(), infolog); else LOGERROR("Failed to link program '%s'+'%s':\n%s", vertexFilePath.string8(), fragmentFilePath.string8(), infolog); delete[] infolog; } ogl_WarnIfError(); if (!ok) return false; Bind(nullptr); ogl_WarnIfError(); // Reorder sampler units to decrease redundant texture unit changes when // samplers bound in a different order. const std::unordered_map requiredUnits = { {CStrIntern("baseTex"), 0}, {CStrIntern("normTex"), 1}, {CStrIntern("specTex"), 2}, {CStrIntern("aoTex"), 3}, {CStrIntern("shadowTex"), 4}, {CStrIntern("losTex"), 5}, }; std::vector occupiedUnits; GLint numUniforms = 0; glGetProgramiv(m_Program, GL_ACTIVE_UNIFORMS, &numUniforms); ogl_WarnIfError(); for (GLint i = 0; i < numUniforms; ++i) { // TODO: use GL_ACTIVE_UNIFORM_MAX_LENGTH for the size. char name[256] = {0}; GLsizei nameLength = 0; GLint size = 0; GLenum type = 0; glGetActiveUniform(m_Program, i, ARRAY_SIZE(name), &nameLength, &size, &type, name); ogl_WarnIfError(); const GLint location = glGetUniformLocation(m_Program, name); // OpenGL specification is a bit vague about a name returned by glGetActiveUniform. // NVIDIA drivers return uniform name with "[0]", Intel Windows drivers without; while (nameLength > 3 && name[nameLength - 3] == '[' && name[nameLength - 2] == '0' && name[nameLength - 1] == ']') { nameLength -= 3; } name[nameLength] = 0; const CStrIntern nameIntern(name); m_BindingSlotsMapping[nameIntern] = m_BindingSlots.size(); BindingSlot bindingSlot{}; bindingSlot.name = nameIntern; bindingSlot.location = location; bindingSlot.size = size; bindingSlot.type = type; bindingSlot.isTexture = false; #define CASE(TYPE, ELEMENT_TYPE, ELEMENT_COUNT) \ case GL_ ## TYPE: \ bindingSlot.elementType = GL_ ## ELEMENT_TYPE; \ bindingSlot.elementCount = ELEMENT_COUNT; \ break; switch (type) { CASE(FLOAT, FLOAT, 1); CASE(FLOAT_VEC2, FLOAT, 2); CASE(FLOAT_VEC3, FLOAT, 3); CASE(FLOAT_VEC4, FLOAT, 4); CASE(INT, INT, 1); CASE(FLOAT_MAT2, FLOAT, 4); CASE(FLOAT_MAT3, FLOAT, 9); CASE(FLOAT_MAT4, FLOAT, 16); #if !CONFIG2_GLES // GL ES 2.0 doesn't support non-square matrices. CASE(FLOAT_MAT2x3, FLOAT, 6); CASE(FLOAT_MAT2x4, FLOAT, 8); CASE(FLOAT_MAT3x2, FLOAT, 6); CASE(FLOAT_MAT3x4, FLOAT, 12); CASE(FLOAT_MAT4x2, FLOAT, 8); CASE(FLOAT_MAT4x3, FLOAT, 12); #endif } #undef CASE // Assign sampler uniforms to sequential texture units. if (type == GL_SAMPLER_2D || type == GL_SAMPLER_CUBE #if !CONFIG2_GLES || type == GL_SAMPLER_2D_SHADOW #endif ) { const auto it = requiredUnits.find(nameIntern); const int unit = it == requiredUnits.end() ? -1 : it->second; bindingSlot.elementType = (type == GL_SAMPLER_CUBE ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D); bindingSlot.elementCount = unit; bindingSlot.isTexture = true; if (unit != -1) { if (unit >= static_cast(occupiedUnits.size())) occupiedUnits.resize(unit + 1); occupiedUnits[unit] = true; } } if (bindingSlot.elementType == 0) { LOGERROR("CShaderProgramGLSL::Link: unsupported uniform type: 0x%04x", static_cast(type)); } m_BindingSlots.emplace_back(std::move(bindingSlot)); } for (BindingSlot& bindingSlot : m_BindingSlots) { if (!bindingSlot.isTexture) continue; if (bindingSlot.elementCount == -1) { // We need to find a minimal available unit. int unit = 0; while (unit < static_cast(occupiedUnits.size()) && occupiedUnits[unit]) ++unit; if (unit >= static_cast(occupiedUnits.size())) occupiedUnits.resize(unit + 1); occupiedUnits[unit] = true; bindingSlot.elementCount = unit; } // Link uniform to unit. glUniform1i(bindingSlot.location, bindingSlot.elementCount); ogl_WarnIfError(); } // TODO: verify that we're not using more samplers than is supported Unbind(); return true; } void Bind(CShaderProgram* previousShaderProgram) override { CShaderProgramGLSL* previousShaderProgramGLSL = nullptr; if (previousShaderProgram) previousShaderProgramGLSL = static_cast(previousShaderProgram); ENSURE(this != previousShaderProgramGLSL); glUseProgram(m_Program); if (previousShaderProgramGLSL) { std::vector::iterator itPrevious = previousShaderProgramGLSL->m_ActiveVertexAttributes.begin(); std::vector::iterator itNext = m_ActiveVertexAttributes.begin(); while ( itPrevious != previousShaderProgramGLSL->m_ActiveVertexAttributes.end() || itNext != m_ActiveVertexAttributes.end()) { if (itPrevious != previousShaderProgramGLSL->m_ActiveVertexAttributes.end() && itNext != m_ActiveVertexAttributes.end()) { if (*itPrevious == *itNext) { ++itPrevious; ++itNext; } else if (*itPrevious < *itNext) { glDisableVertexAttribArray(*itPrevious); ++itPrevious; } else if (*itPrevious > *itNext) { glEnableVertexAttribArray(*itNext); ++itNext; } } else if (itPrevious != previousShaderProgramGLSL->m_ActiveVertexAttributes.end()) { glDisableVertexAttribArray(*itPrevious); ++itPrevious; } else if (itNext != m_ActiveVertexAttributes.end()) { glEnableVertexAttribArray(*itNext); ++itNext; } } } else { for (const int index : m_ActiveVertexAttributes) glEnableVertexAttribArray(index); } m_ValidStreams = 0; } void Unbind() override { glUseProgram(0); for (const int index : m_ActiveVertexAttributes) glDisableVertexAttribArray(index); } IDevice* GetDevice() override { return m_Device; } int32_t GetBindingSlot(const CStrIntern name) const override { auto it = m_BindingSlotsMapping.find(name); return it == m_BindingSlotsMapping.end() ? -1 : it->second; } TextureUnit GetTextureUnit(const int32_t bindingSlot) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return { 0, 0, 0 }; TextureUnit textureUnit; textureUnit.type = m_BindingSlots[bindingSlot].type; textureUnit.target = m_BindingSlots[bindingSlot].elementType; textureUnit.unit = m_BindingSlots[bindingSlot].elementCount; return textureUnit; } void SetUniform( const int32_t bindingSlot, const float value) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].type != GL_FLOAT || m_BindingSlots[bindingSlot].size != 1) { LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected float) '%s'", m_BindingSlots[bindingSlot].name.c_str()); return; } glUniform1f(m_BindingSlots[bindingSlot].location, value); ogl_WarnIfError(); } void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC2 || m_BindingSlots[bindingSlot].size != 1) { LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected vec2) '%s'", m_BindingSlots[bindingSlot].name.c_str()); return; } glUniform2f(m_BindingSlots[bindingSlot].location, valueX, valueY); ogl_WarnIfError(); } void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC3 || m_BindingSlots[bindingSlot].size != 1) { LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected vec3) '%s'", m_BindingSlots[bindingSlot].name.c_str()); return; } glUniform3f(m_BindingSlots[bindingSlot].location, valueX, valueY, valueZ); ogl_WarnIfError(); } void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ, const float valueW) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC4 || m_BindingSlots[bindingSlot].size != 1) { LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected vec4) '%s'", m_BindingSlots[bindingSlot].name.c_str()); return; } glUniform4f(m_BindingSlots[bindingSlot].location, valueX, valueY, valueZ, valueW); ogl_WarnIfError(); } void SetUniform( const int32_t bindingSlot, PS::span values) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].elementType != GL_FLOAT) { LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform element type (expected float) '%s'", m_BindingSlots[bindingSlot].name.c_str()); return; } if (m_BindingSlots[bindingSlot].size == 1 && m_BindingSlots[bindingSlot].elementCount > static_cast(values.size())) { LOGERROR( "CShaderProgramGLSL::SetUniform(): Invalid uniform element count (expected: %zu passed: %zu) '%s'", m_BindingSlots[bindingSlot].elementCount, values.size(), m_BindingSlots[bindingSlot].name.c_str()); return; } const GLint location = m_BindingSlots[bindingSlot].location; const GLenum type = m_BindingSlots[bindingSlot].type; if (type == GL_FLOAT) glUniform1fv(location, 1, values.data()); else if (type == GL_FLOAT_VEC2) glUniform2fv(location, 1, values.data()); else if (type == GL_FLOAT_VEC3) glUniform3fv(location, 1, values.data()); else if (type == GL_FLOAT_VEC4) glUniform4fv(location, 1, values.data()); else if (type == GL_FLOAT_MAT4) { // For case of array of matrices we might pass less number of matrices. const GLint size = std::min( m_BindingSlots[bindingSlot].size, static_cast(values.size() / 16)); glUniformMatrix4fv(location, size, GL_FALSE, values.data()); } else LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected float, vec2, vec3, vec4, mat4) '%s'", m_BindingSlots[bindingSlot].name.c_str()); ogl_WarnIfError(); } void VertexAttribPointer( const VertexAttributeStream stream, const Format format, - const uint32_t offset, const uint32_t stride, const void* data) override + const uint32_t offset, const uint32_t stride, + const VertexAttributeRate rate, const void* data) override { const int attributeLocation = GetAttributeLocationFromStream(m_Device, stream); std::vector::const_iterator it = std::lower_bound(m_ActiveVertexAttributes.begin(), m_ActiveVertexAttributes.end(), attributeLocation); if (it == m_ActiveVertexAttributes.end() || *it != attributeLocation) return; const GLint size = GLSizeFromFormat(format); const GLenum type = GLTypeFromFormat(format); const GLboolean normalized = NormalizedFromFormat(format); glVertexAttribPointer( attributeLocation, size, type, normalized, stride, static_cast(data) + offset); + if (rate == VertexAttributeRate::PER_INSTANCE) + ENSURE(m_Device->GetCapabilities().instancing); + if (m_Device->GetCapabilities().instancing) + { + glVertexAttribDivisorARB(attributeLocation, rate == VertexAttributeRate::PER_INSTANCE ? 1 : 0); + } m_ValidStreams |= GetStreamMask(stream); } std::vector GetFileDependencies() const override { return m_FileDependencies; } private: CDevice* m_Device = nullptr; CStr m_Name; std::vector m_FileDependencies; std::map m_VertexAttribs; // Sorted list of active vertex attributes. std::vector m_ActiveVertexAttributes; GLuint m_Program; GLuint m_VertexShader, m_FragmentShader; struct BindingSlot { CStrIntern name; GLint location; GLint size; GLenum type; GLenum elementType; GLint elementCount; bool isTexture; }; std::vector m_BindingSlots; std::unordered_map m_BindingSlotsMapping; }; CShaderProgram::CShaderProgram(int streamflags) : m_StreamFlags(streamflags), m_ValidStreams(0) { } CShaderProgram::~CShaderProgram() = default; // static std::unique_ptr CShaderProgram::Create(CDevice* device, const CStr& name, const CShaderDefines& baseDefines) { PROFILE2("loading shader"); PROFILE2_ATTR("name: %s", name.c_str()); VfsPath xmlFilename = L"shaders/" + wstring_from_utf8(name) + L".xml"; CXeromyces XeroFile; PSRETURN ret = XeroFile.Load(g_VFS, xmlFilename); if (ret != PSRETURN_OK) return nullptr; #if USE_SHADER_XML_VALIDATION { // Serialize the XMB data and pass it to the validator XMLWriter_File shaderFile; shaderFile.SetPrettyPrint(false); shaderFile.XMB(XeroFile); bool ok = CXeromyces::ValidateEncoded("shader", name, shaderFile.GetOutput()); if (!ok) return nullptr; } #endif // Define all the elements and attributes used in the XML file #define EL(x) int el_##x = XeroFile.GetElementID(#x) #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) EL(define); EL(fragment); EL(stream); EL(uniform); EL(vertex); AT(attribute); AT(file); AT(if); AT(loc); AT(name); AT(type); AT(value); #undef AT #undef EL CPreprocessorWrapper preprocessor; preprocessor.AddDefines(baseDefines); XMBElement root = XeroFile.GetRoot(); const bool isGLSL = root.GetAttributes().GetNamedItem(at_type) == "glsl"; VfsPath vertexFile; VfsPath fragmentFile; CShaderDefines defines = baseDefines; std::map> vertexUniforms; std::map> fragmentUniforms; std::map vertexAttribs; int streamFlags = 0; XERO_ITER_EL(root, child) { if (child.GetNodeName() == el_define) { defines.Add(CStrIntern(child.GetAttributes().GetNamedItem(at_name)), CStrIntern(child.GetAttributes().GetNamedItem(at_value))); } else if (child.GetNodeName() == el_vertex) { vertexFile = L"shaders/" + child.GetAttributes().GetNamedItem(at_file).FromUTF8(); XERO_ITER_EL(child, param) { XMBAttributeList attributes = param.GetAttributes(); CStr cond = attributes.GetNamedItem(at_if); if (!cond.empty() && !preprocessor.TestConditional(cond)) continue; if (param.GetNodeName() == el_uniform) { vertexUniforms[CStrIntern(attributes.GetNamedItem(at_name))] = std::make_pair(attributes.GetNamedItem(at_type), attributes.GetNamedItem(at_loc).ToInt()); } else if (param.GetNodeName() == el_stream) { const CStr streamName = attributes.GetNamedItem(at_name); const CStr attributeName = attributes.GetNamedItem(at_attribute); if (attributeName.empty() && isGLSL) LOGERROR("Empty attribute name in vertex shader description '%s'", vertexFile.string8().c_str()); VertexAttributeStream stream = VertexAttributeStream::UV7; if (streamName == "pos") stream = VertexAttributeStream::POSITION; else if (streamName == "normal") stream = VertexAttributeStream::NORMAL; else if (streamName == "color") stream = VertexAttributeStream::COLOR; else if (streamName == "uv0") stream = VertexAttributeStream::UV0; else if (streamName == "uv1") stream = VertexAttributeStream::UV1; else if (streamName == "uv2") stream = VertexAttributeStream::UV2; else if (streamName == "uv3") stream = VertexAttributeStream::UV3; else if (streamName == "uv4") stream = VertexAttributeStream::UV4; else if (streamName == "uv5") stream = VertexAttributeStream::UV5; else if (streamName == "uv6") stream = VertexAttributeStream::UV6; else if (streamName == "uv7") stream = VertexAttributeStream::UV7; else LOGERROR("Unknown stream '%s' in vertex shader description '%s'", streamName.c_str(), vertexFile.string8().c_str()); if (isGLSL) { const int attributeLocation = GetAttributeLocationFromStream(device, stream); vertexAttribs[CStrIntern(attributeName)] = attributeLocation; } streamFlags |= GetStreamMask(stream); } } } else if (child.GetNodeName() == el_fragment) { fragmentFile = L"shaders/" + child.GetAttributes().GetNamedItem(at_file).FromUTF8(); XERO_ITER_EL(child, param) { XMBAttributeList attributes = param.GetAttributes(); CStr cond = attributes.GetNamedItem(at_if); if (!cond.empty() && !preprocessor.TestConditional(cond)) continue; if (param.GetNodeName() == el_uniform) { fragmentUniforms[CStrIntern(attributes.GetNamedItem(at_name))] = std::make_pair(attributes.GetNamedItem(at_type), attributes.GetNamedItem(at_loc).ToInt()); } } } } if (isGLSL) { return std::make_unique( device, name, xmlFilename, vertexFile, fragmentFile, defines, vertexAttribs, streamFlags); } else { #if CONFIG2_GLES LOGERROR("CShaderProgram::Create: '%s'+'%s': ARB shaders not supported on this device", vertexFile.string8(), fragmentFile.string8()); return nullptr; #else return std::make_unique( device, xmlFilename, vertexFile, fragmentFile, defines, vertexUniforms, fragmentUniforms, streamFlags); #endif } } // These should all be overridden by CShaderProgramGLSL, and not used // if a non-GLSL shader was loaded instead: -void CShaderProgram::VertexAttribPointer(attrib_id_t UNUSED(id), const Renderer::Backend::Format UNUSED(format), - GLboolean UNUSED(normalized), GLsizei UNUSED(stride), const void* UNUSED(pointer)) -{ - debug_warn("Shader type doesn't support VertexAttribPointer"); -} - #if CONFIG2_GLES // These should all be overridden by CShaderProgramGLSL // (GLES doesn't support any other types of shader program): void CShaderProgram::VertexPointer(const Renderer::Backend::Format UNUSED(format), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::VertexPointer should be overridden"); } void CShaderProgram::NormalPointer(const Renderer::Backend::Format UNUSED(format), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::NormalPointer should be overridden"); } void CShaderProgram::ColorPointer(const Renderer::Backend::Format UNUSED(format), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::ColorPointer should be overridden"); } void CShaderProgram::TexCoordPointer(GLenum UNUSED(texture), const Renderer::Backend::Format UNUSED(format), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::TexCoordPointer should be overridden"); } #else // These are overridden by CShaderProgramGLSL, but fixed-function and ARB shaders // both use the fixed-function vertex attribute pointers so we'll share their // definitions here: void CShaderProgram::VertexPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer) { const GLint size = GLSizeFromFormat(format); ENSURE(2 <= size && size <= 4); const GLenum type = GLTypeFromFormat(format); glVertexPointer(size, type, stride, pointer); m_ValidStreams |= GetStreamMask(VertexAttributeStream::POSITION); } void CShaderProgram::NormalPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer) { ENSURE(format == Renderer::Backend::Format::R32G32B32_SFLOAT); glNormalPointer(GL_FLOAT, stride, pointer); m_ValidStreams |= GetStreamMask(VertexAttributeStream::NORMAL); } void CShaderProgram::ColorPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer) { const GLint size = GLSizeFromFormat(format); ENSURE(3 <= size && size <= 4); const GLenum type = GLTypeFromFormat(format); glColorPointer(size, type, stride, pointer); m_ValidStreams |= GetStreamMask(VertexAttributeStream::COLOR); } void CShaderProgram::TexCoordPointer(GLenum texture, const Renderer::Backend::Format format, GLsizei stride, const void* pointer) { glClientActiveTextureARB(texture); const GLint size = GLSizeFromFormat(format); ENSURE(1 <= size && size <= 4); const GLenum type = GLTypeFromFormat(format); glTexCoordPointer(size, type, stride, pointer); glClientActiveTextureARB(GL_TEXTURE0); m_ValidStreams |= GetStreamMask(VertexAttributeStream::UV0) << (texture - GL_TEXTURE0); } void CShaderProgram::BindClientStates() { ENSURE(m_StreamFlags == (m_StreamFlags & ( GetStreamMask(VertexAttributeStream::POSITION) | GetStreamMask(VertexAttributeStream::NORMAL) | GetStreamMask(VertexAttributeStream::COLOR) | GetStreamMask(VertexAttributeStream::UV0) | GetStreamMask(VertexAttributeStream::UV1)))); // Enable all the desired client states for non-GLSL rendering if (m_StreamFlags & GetStreamMask(VertexAttributeStream::POSITION)) glEnableClientState(GL_VERTEX_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::NORMAL)) glEnableClientState(GL_NORMAL_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::COLOR)) glEnableClientState(GL_COLOR_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::UV0)) { glClientActiveTextureARB(GL_TEXTURE0); glEnableClientState(GL_TEXTURE_COORD_ARRAY); } if (m_StreamFlags & GetStreamMask(VertexAttributeStream::UV1)) { glClientActiveTextureARB(GL_TEXTURE1); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTextureARB(GL_TEXTURE0); } // Rendering code must subsequently call VertexPointer etc for all of the streams // that were activated in this function, else AssertPointersBound will complain // that some arrays were unspecified m_ValidStreams = 0; } void CShaderProgram::UnbindClientStates() { if (m_StreamFlags & GetStreamMask(VertexAttributeStream::POSITION)) glDisableClientState(GL_VERTEX_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::NORMAL)) glDisableClientState(GL_NORMAL_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::COLOR)) glDisableClientState(GL_COLOR_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::UV0)) { glClientActiveTextureARB(GL_TEXTURE0); glDisableClientState(GL_TEXTURE_COORD_ARRAY); } if (m_StreamFlags & GetStreamMask(VertexAttributeStream::UV1)) { glClientActiveTextureARB(GL_TEXTURE1); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTextureARB(GL_TEXTURE0); } } #endif // !CONFIG2_GLES bool CShaderProgram::IsStreamActive(const VertexAttributeStream stream) const { return (m_StreamFlags & GetStreamMask(stream)) != 0; } void CShaderProgram::VertexAttribPointer( const VertexAttributeStream stream, const Format format, - const uint32_t offset, const uint32_t stride, const void* data) + const uint32_t offset, const uint32_t stride, + const VertexAttributeRate rate, const void* data) { + ENSURE(rate == VertexAttributeRate::PER_VERTEX); switch (stream) { case VertexAttributeStream::POSITION: VertexPointer(format, stride, static_cast(data) + offset); break; case VertexAttributeStream::NORMAL: NormalPointer(format, stride, static_cast(data) + offset); break; case VertexAttributeStream::COLOR: ColorPointer(format, stride, static_cast(data) + offset); break; case VertexAttributeStream::UV0: FALLTHROUGH; case VertexAttributeStream::UV1: FALLTHROUGH; case VertexAttributeStream::UV2: FALLTHROUGH; case VertexAttributeStream::UV3: FALLTHROUGH; case VertexAttributeStream::UV4: FALLTHROUGH; case VertexAttributeStream::UV5: FALLTHROUGH; case VertexAttributeStream::UV6: FALLTHROUGH; case VertexAttributeStream::UV7: { const int indexOffset = static_cast(stream) - static_cast(VertexAttributeStream::UV0); TexCoordPointer(GL_TEXTURE0 + indexOffset, format, stride, static_cast(data) + offset); break; } default: debug_warn("Unsupported stream"); }; } void CShaderProgram::AssertPointersBound() { ENSURE((m_StreamFlags & ~m_ValidStreams) == 0); } } // namespace GL } // namespace Backend } // namespace Renderer