/
redgpu
/
ezEngine
Обзор
Документация
Войти
/
redgpu
/
ezEngine
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
Code/Engine/RendererCore/RenderContext/Implementation/RenderContext.cpp
1 281 строка
51 KB
Jan Krassnigg
Fixed #1954: Added CVar to change the default texture filtering mode (#1957)
08 июн 2026, 12:57
Не верифицирован
08 июн 2026, 12:57
8e163d9
Код
Авторство
О чём код?
#include <RendererCore/RendererCorePCH.h> #include <Foundation/Algorithm/HashStream.h> #include <Foundation/Configuration/CVar.h> #include <Foundation/Configuration/Startup.h> #include <Foundation/Time/Clock.h> #include <Foundation/Types/ScopeExit.h> #include <Foundation/Utilities/Stats.h> #include <RendererCore/Material/MaterialManager.h> #include <RendererCore/Material/MaterialResource.h> #include <RendererCore/Meshes/DynamicMeshBufferResource.h> #include <RendererCore/RenderContext/BindGroupBuilder.h> #include <RendererCore/RenderContext/RenderContext.h> #include <RendererCore/RenderWorld/RenderWorld.h> #include <RendererCore/Shader/ShaderPermutationResource.h> #include <RendererCore/ShaderCompiler/ShaderManager.h> #include <RendererCore/Textures/Texture2DResource.h> #include <RendererCore/Textures/Texture3DResource.h> #include <RendererCore/Textures/TextureCubeResource.h> #include <RendererFoundation/CommandEncoder/CommandEncoder.h> #include <RendererFoundation/Device/ImmutableSamplers.h> #include <RendererFoundation/Resources/Buffer.h> #include <RendererFoundation/Resources/ProxyTexture.h> #include <RendererFoundation/Resources/RenderTargetView.h> #include <RendererFoundation/Resources/Texture.h> #include <RendererFoundation/Shader/Shader.h> #include <RendererFoundation/State/PipelineCache.h> ezRenderContext* ezRenderContext::s_pDefaultInstance = nullptr; ezGALCommandEncoder* ezRenderContext::s_pCommandEncoder = nullptr; ezHybridArray<ezRenderContext*, 4> ezRenderContext::s_Instances; // 0=Nearest, 1=Bilinear, 2=Trilinear, 3=Aniso2x, 4=Aniso4x, 5=Aniso8x, 6=Aniso16x ezCVarInt cvar_RenderingTextureQuality("Rendering.TextureQuality", 4, ezCVarFlags::Save, "Default texture filtering quality. 0=Nearest, 1=Bilinear, 2=Trilinear, 3=Anisotropic2x, 4=Anisotropic4x, 5=Anisotropic8x, 6=Anisotropic16x."); ezMap<ezRenderContext::ShaderVertexDecl, ezGALVertexDeclarationHandle> ezRenderContext::s_GALVertexDeclarations; ezMutex ezRenderContext::s_ConstantBufferStorageMutex; ezIdTable<ezConstantBufferStorageId, ezConstantBufferStorageBase*> ezRenderContext::s_ConstantBufferStorageTable; ezMap<ezUInt32, ezDynamicArray<ezConstantBufferStorageBase*>> ezRenderContext::s_FreeConstantBufferStorage; ezSet<ezConstantBufferStorageBase*> ezRenderContext::s_DirtyConstantBuffers; namespace { ezUInt32 GetVertexBufferStride(ezGALDevice* pDevice, ezGALBufferHandle hBuffer) { if (!hBuffer.IsInvalidated()) { if (const ezGALBuffer* pBuffer = pDevice->GetBuffer(hBuffer)) { return pBuffer->GetDescription().m_uiStructSize; } } return 0; } } // namespace // clang-format off EZ_BEGIN_SUBSYSTEM_DECLARATION(RendererCore, RendererContext) BEGIN_SUBSYSTEM_DEPENDENCIES "Foundation", "Core", "ImmutableSamplers" END_SUBSYSTEM_DEPENDENCIES ON_CORESYSTEMS_STARTUP { ezRenderContext::RegisterImmutableSamplers(); ezGALDevice::s_Events.AddEventHandler(ezMakeDelegate(&ezRenderContext::GALStaticDeviceEventHandler)); } ON_CORESYSTEMS_SHUTDOWN { ezGALDevice::s_Events.RemoveEventHandler(ezMakeDelegate(&ezRenderContext::GALStaticDeviceEventHandler)); } ON_HIGHLEVELSYSTEMS_STARTUP { ezRenderContext::OnEngineStartup(); } ON_HIGHLEVELSYSTEMS_SHUTDOWN { ezRenderContext::OnEngineShutdown(); } EZ_END_SUBSYSTEM_DECLARATION; // clang-format on ////////////////////////////////////////////////////////////////////////// ezRenderContext::Statistics::Statistics() { Reset(); } void ezRenderContext::Statistics::Reset() { m_uiFailedDrawcalls = 0; for (ezUInt32 i = 0; i < EZ_GAL_MAX_BIND_GROUPS; ++i) { m_uiModifiedBindGroup[i] = 0; m_uiLayoutChanged[i] = 0; } } ////////////////////////////////////////////////////////////////////////// ezRenderContext* ezRenderContext::GetDefaultInstance() { if (s_pDefaultInstance == nullptr) s_pDefaultInstance = CreateInstance(s_pCommandEncoder); EZ_ASSERT_DEBUG(s_pDefaultInstance != nullptr, "Default instance should have been created during device creation"); return s_pDefaultInstance; } ezRenderContext* ezRenderContext::CreateInstance(ezGALCommandEncoder* pCommandEncoder) { return EZ_DEFAULT_NEW(ezRenderContext, pCommandEncoder); } void ezRenderContext::DestroyInstance(ezRenderContext* pRenderer) { EZ_DEFAULT_DELETE(pRenderer); } ezRenderContext::ezRenderContext(ezGALCommandEncoder* pCommandEncoder) { s_Instances.PushBack(this); m_pGALCommandEncoder = pCommandEncoder; m_StateFlags = ezRenderContextFlags::AllStatesInvalid; m_GraphicsPipeline.m_Topology = ezGALPrimitiveTopology::ENUM_COUNT; // Set to something invalid m_uiMeshBufferPrimitiveCount = 0; m_bAllowAsyncShaderLoading = false; m_hGlobalConstantBufferStorage = CreateConstantBufferStorage<ezGlobalConstants>(); // If no push constants are supported, they are emulated via constant buffers. if (ezGALDevice::GetDefaultDevice()->GetCapabilities().m_uiMaxPushConstantsSize == 0) { m_hPushConstantsStorage = CreateConstantBufferStorage(128); } ResetContextState(); } ezRenderContext::~ezRenderContext() { DeleteConstantBufferStorage(m_hGlobalConstantBufferStorage); DeleteConstantBufferStorage(m_hPushConstantsStorage); if (s_pDefaultInstance == this) s_pDefaultInstance = nullptr; s_Instances.RemoveAndSwap(this); } ezRenderContext::Statistics ezRenderContext::GetAndResetStatistics() { ezRenderContext::Statistics ret = m_Statistics; m_Statistics.Reset(); return ret; } void ezRenderContext::BeginRendering(const ezGALRenderingSetup& renderingSetup, const ezRectFloat& viewport, const char* szName, bool bStereoSupport) { EZ_ASSERT_DEBUG(m_bRendering == false && m_bCompute == false, "Already in a scope"); m_bRendering = true; m_GraphicsPipeline.m_RenderPass = renderingSetup.GetRenderPass(); m_StateFlags.Add(ezRenderContextFlags::PipelineChanged); const ezGALMSAASampleCount::Enum msaaSampleCount = renderingSetup.GetRenderPass().m_Msaa; if (msaaSampleCount != ezGALMSAASampleCount::None) { SetShaderPermutationVariable("MSAA", "TRUE"); } else { SetShaderPermutationVariable("MSAA", "FALSE"); } auto& gc = WriteGlobalConstants(); gc.ViewportSize = ezVec4(viewport.width, viewport.height, 1.0f / viewport.width, 1.0f / viewport.height); gc.NumMsaaSamples = msaaSampleCount; m_pGALCommandEncoder->BeginRendering(renderingSetup, szName); m_pGALCommandEncoder->SetViewport(viewport); m_bStereoRendering = bStereoSupport; } void ezRenderContext::EndRendering() { m_pGALCommandEncoder->EndRendering(); m_bStereoRendering = false; m_bRendering = false; } void ezRenderContext::BeginCompute(const char* szName /*= ""*/) { EZ_ASSERT_DEBUG(m_bRendering == false && m_bCompute == false, "Already in a scope"); m_pGALCommandEncoder->BeginCompute(szName); m_bCompute = true; m_StateFlags.Add(ezRenderContextFlags::PipelineChanged); } void ezRenderContext::EndCompute() { m_pGALCommandEncoder->EndCompute(); m_bCompute = false; m_StateFlags.Add(ezRenderContextFlags::PipelineChanged); } void ezRenderContext::SetShaderPermutationVariable(const char* szName, const ezTempHashedString& sTempValue) { ezTempHashedString sHashedName(szName); ezHashedString sName; ezHashedString sValue; if (ezShaderManager::IsPermutationValueAllowed(szName, sHashedName, sTempValue, sName, sValue)) { SetShaderPermutationVariableInternal(sName, sValue); } } void ezRenderContext::SetShaderPermutationVariable(const ezHashedString& sName, const ezHashedString& sValue) { if (ezShaderManager::IsPermutationValueAllowed(sName, sValue)) { SetShaderPermutationVariableInternal(sName, sValue); } } void ezRenderContext::BindMaterial(const ezMaterialResourceHandle& hMaterial) { // Don't set m_hMaterial directly since we first need to check whether the material has been modified in the mean time. m_hNewMaterial = hMaterial; m_StateFlags.Add(ezRenderContextFlags::MaterialBindingChanged | ezRenderContextFlags::BindGroupChanged); m_bDirtyBindGroups[EZ_GAL_BIND_GROUP_MATERIAL] = true; } ezBindGroupBuilder& ezRenderContext::GetBindGroup(ezUInt32 uiBindGroup) { EZ_ASSERT_DEBUG(uiBindGroup <= EZ_GAL_MAX_BIND_GROUPS, "Bind group out of range"); return m_BindGroupBuilders[uiBindGroup]; } void ezRenderContext::ResetBindGroup(ezUInt32 uiBindGroup) { m_BindGroupBuilders[uiBindGroup].ResetBoundResources(ezGALDevice::GetDefaultDevice()); m_BindGroups[uiBindGroup].m_hBindGroupLayout.Invalidate(); m_BindGroups[uiBindGroup].m_BindGroupItems.Clear(); m_bDirtyBindGroups[uiBindGroup] = false; } void ezRenderContext::ResetBindGroups() { for (ezUInt32 i = 0; i < EZ_GAL_MAX_BIND_GROUPS; ++i) { ResetBindGroup(i); } } void ezRenderContext::SetPushConstants(ezTempHashedString sSlotName, ezArrayPtr<const ezUInt8> data) { if (!m_hPushConstantsStorage.IsInvalidated()) { EZ_ASSERT_DEBUG(data.GetCount() <= 128, "Push constants are not allowed to be bigger than 128 bytes."); ezConstantBufferStorageBase* pStorage = nullptr; bool bResult = TryGetConstantBufferStorage(m_hPushConstantsStorage, pStorage); if (bResult) { ezArrayPtr<ezUInt8> targetStorage = pStorage->GetRawDataForWriting(); ezMemoryUtils::Copy(targetStorage.GetPtr(), data.GetPtr(), data.GetCount()); ezBindGroupBuilder& bindGroupDraw = ezRenderContext::GetDefaultInstance()->GetBindGroup(EZ_GAL_BIND_GROUP_DRAW_CALL); bindGroupDraw.BindBuffer(sSlotName, m_hPushConstantsStorage); } } else { EZ_ASSERT_DEBUG(data.GetCount() <= ezGALDevice::GetDefaultDevice()->GetCapabilities().m_uiMaxPushConstantsSize, "Push constants are not allowed to be bigger than {} bytes.", ezGALDevice::GetDefaultDevice()->GetCapabilities().m_uiMaxPushConstantsSize); m_pGALCommandEncoder->SetPushConstants(data); } } void ezRenderContext::BindShader(const ezShaderResourceHandle& hShader, ezBitflags<ezShaderBindFlags> flags) { m_hMaterial.Invalidate(); m_pMaterial = nullptr; m_pMaterialBindGroup = nullptr; m_bDirtyBindGroups[EZ_GAL_BIND_GROUP_MATERIAL] = true; m_StateFlags.Remove(ezRenderContextFlags::MaterialBindingChanged); m_StateFlags.Add(ezRenderContextFlags::BindGroupChanged); BindShaderInternal(hShader, flags); } void ezRenderContext::SetBlendState(ezGALBlendStateHandle hBlendState) { m_GraphicsPipeline.m_hBlendState = hBlendState; m_StateFlags.Add(ezRenderContextFlags::PipelineChanged); } void ezRenderContext::SetDepthStencilState(ezGALDepthStencilStateHandle hDepthStencilState) { m_GraphicsPipeline.m_hDepthStencilState = hDepthStencilState; m_StateFlags.Add(ezRenderContextFlags::PipelineChanged); } void ezRenderContext::SetRasterizerState(ezGALRasterizerStateHandle hRasterizerState) { m_GraphicsPipeline.m_hRasterizerState = hRasterizerState; m_StateFlags.Add(ezRenderContextFlags::PipelineChanged); } void ezRenderContext::SetStencilRefValue(ezUInt8 uiStencilRefValue) { m_uiUserStencilRefValue = uiStencilRefValue; m_StateFlags.Add(ezRenderContextFlags::NonPipelineStateChanged); } void ezRenderContext::BindMeshBuffer(const ezMeshBufferResourceHandle& hMeshBuffer, ezGALBufferHandle hDataOffsetsBuffer /*= {}*/, ezUInt32 uiFirstDataOffset /*= 0*/) { ezResourceLock<ezMeshBufferResource> pMeshBuffer(hMeshBuffer, ezResourceAcquireMode::AllowLoadingFallback); BindMeshBuffer(pMeshBuffer->GetVertexBuffers(), pMeshBuffer->GetIndexBuffer(), pMeshBuffer->GetVertexAttributes(), pMeshBuffer->GetTopology(), pMeshBuffer->GetPrimitiveCount(), hDataOffsetsBuffer, uiFirstDataOffset); } void ezRenderContext::BindMeshBuffer(const ezDynamicMeshBufferResourceHandle& hDynamicMeshBuffer, ezGALBufferHandle hDataOffsetsBuffer /*= {}*/, ezUInt32 uiFirstDataOffset /*= 0*/) { ezResourceLock<ezDynamicMeshBufferResource> pMeshBuffer(hDynamicMeshBuffer, ezResourceAcquireMode::AllowLoadingFallback); BindMeshBuffer(pMeshBuffer->GetVertexBuffers(), pMeshBuffer->GetIndexBuffer(), pMeshBuffer->GetVertexAttributes(), pMeshBuffer->GetDescriptor().m_Topology, pMeshBuffer->GetDescriptor().m_uiMaxPrimitives, hDataOffsetsBuffer, uiFirstDataOffset); } void ezRenderContext::BindMeshBuffer(ezArrayPtr<const ezGALBufferHandle> vertexBuffers, ezGALBufferHandle hIndexBuffer, ezArrayPtr<const ezGALVertexAttribute> vertexAttributes, ezGALPrimitiveTopology::Enum topology, ezUInt32 uiPrimitiveCount, ezGALBufferHandle hDataOffsetsBuffer /*= {}*/, ezUInt32 uiFirstDataOffset /*= 0*/) { constexpr ezUInt32 uiMaxNumVertexBuffers = EZ_ARRAY_SIZE(m_hVertexBuffers); constexpr ezUInt32 uiDataOffsetsBufferSlot = ezMeshVertexStreamType::DataOffsets; EZ_ASSERT_DEBUG(vertexBuffers.GetCount() <= uiDataOffsetsBufferSlot, "Too many vertex buffers"); // We need to create a new array to ensure that unsused slots are set to invalid ezGALBufferHandle newVertexBuffers[uiMaxNumVertexBuffers] = {}; ezMemoryUtils::Copy(newVertexBuffers, vertexBuffers.GetPtr(), vertexBuffers.GetCount()); newVertexBuffers[uiDataOffsetsBufferSlot] = hDataOffsetsBuffer; if (ezMemoryUtils::IsEqual(m_hVertexBuffers, newVertexBuffers, uiMaxNumVertexBuffers) && m_hIndexBuffer == hIndexBuffer && m_VertexAttributes == vertexAttributes && m_GraphicsPipeline.m_Topology == topology && m_uiMeshBufferPrimitiveCount == uiPrimitiveCount) { return; } #if EZ_ENABLED(EZ_COMPILE_FOR_DEBUG) for (ezUInt32 i1 = 0; i1 < vertexAttributes.GetCount(); ++i1) { for (ezUInt32 i2 = 0; i2 < vertexAttributes.GetCount(); ++i2) { if (i1 != i2) { EZ_ASSERT_DEBUG(vertexAttributes[i1].m_eSemantic != vertexAttributes[i2].m_eSemantic, "Same semantic cannot be used twice in the same vertex declaration"); } } } EZ_ASSERT_DEBUG(vertexBuffers.IsEmpty() || !vertexAttributes.IsEmpty(), "Needs vertex attributes if vertex buffers are provided"); #endif if (m_GraphicsPipeline.m_Topology != topology) { m_GraphicsPipeline.m_Topology = topology; m_StateFlags.Add(ezRenderContextFlags::PipelineChanged); ezTempHashedString sTopologies[] = { ezTempHashedString("TOPOLOGY_POINTS"), ezTempHashedString("TOPOLOGY_LINES"), ezTempHashedString("TOPOLOGY_TRIANGLES"), ezTempHashedString("TOPOLOGY_TRIANGLESTRIP"), }; static_assert(EZ_ARRAY_SIZE(sTopologies) == ezGALPrimitiveTopology::ENUM_COUNT); SetShaderPermutationVariable("TOPOLOGY", sTopologies[m_GraphicsPipeline.m_Topology]); } ezMemoryUtils::Copy(m_hVertexBuffers, newVertexBuffers, uiMaxNumVertexBuffers); m_hIndexBuffer = hIndexBuffer; m_VertexAttributes = vertexAttributes; m_uiMeshBufferPrimitiveCount = uiPrimitiveCount; ezGALDevice* pDevice = ezGALDevice::GetDefaultDevice(); for (ezUInt32 i = 0; i < vertexBuffers.GetCount(); ++i) { m_VertexBufferStrides[i] = GetVertexBufferStride(pDevice, vertexBuffers[i]); m_VertexBufferOffsets[i] = 0; m_VertexBufferBindingRates[i] = ezGALVertexBindingRate::Vertex; } if (hDataOffsetsBuffer.IsInvalidated() == false) { constexpr ezUInt32 uiDataOffsetStructSize = sizeof(ezInstanceableRenderData::DataOffsets); EZ_ASSERT_DEBUG(GetVertexBufferStride(pDevice, hDataOffsetsBuffer) == uiDataOffsetStructSize, "Wrong buffer stride"); m_VertexBufferStrides[uiDataOffsetsBufferSlot] = uiDataOffsetStructSize; m_VertexBufferOffsets[uiDataOffsetsBufferSlot] = uiFirstDataOffset * uiDataOffsetStructSize; m_VertexBufferBindingRates[uiDataOffsetsBufferSlot] = ezGALVertexBindingRate::Instance; m_VertexAttributes.PushBack(ezMeshVertexStreamConfig::GetDataOffsetsVertexAttribute()); } m_StateFlags.Add(ezRenderContextFlags::MeshBufferBindingChanged); } void ezRenderContext::BindVertexBuffer(ezGALBufferHandle hVertexBuffer, ezUInt32 uiSlot, ezEnum<ezGALVertexBindingRate> rate, ezUInt32 uiOffset) { EZ_ASSERT_DEBUG(uiSlot < EZ_GAL_MAX_VERTEX_BUFFER_COUNT, "Vertex buffer slot is out of bounds"); ezGALDevice* pDevice = ezGALDevice::GetDefaultDevice(); m_hVertexBuffers[uiSlot] = hVertexBuffer; m_VertexBufferStrides[uiSlot] = GetVertexBufferStride(pDevice, hVertexBuffer); m_VertexBufferBindingRates[uiSlot] = rate; m_VertexBufferOffsets[uiSlot] = uiOffset; m_StateFlags.Add(ezRenderContextFlags::MeshBufferBindingChanged); } void ezRenderContext::SetVertexAttributes(ezArrayPtr<ezGALVertexAttribute> vertexAttributes) { if (m_VertexAttributes == vertexAttributes) return; m_VertexAttributes = vertexAttributes; m_StateFlags.Add(ezRenderContextFlags::MeshBufferBindingChanged); } ezResult ezRenderContext::DrawMeshBuffer(ezUInt32 uiPrimitiveCount, ezUInt32 uiFirstPrimitive, ezUInt32 uiInstanceCount) { if (ApplyContextStates().Failed() || uiPrimitiveCount == 0 || uiInstanceCount == 0) { m_Statistics.m_uiFailedDrawcalls++; return EZ_FAILURE; } EZ_ASSERT_DEV(uiFirstPrimitive < m_uiMeshBufferPrimitiveCount, "Invalid primitive range: first primitive ({0}) can't be larger than number of primitives ({1})", uiFirstPrimitive, uiPrimitiveCount); uiPrimitiveCount = ezMath::Min(uiPrimitiveCount, m_uiMeshBufferPrimitiveCount - uiFirstPrimitive); EZ_ASSERT_DEV(uiPrimitiveCount > 0, "Invalid primitive range: number of primitives can't be zero."); auto pCommandEncoder = GetCommandEncoder(); const ezUInt32 uiIndexCount = ezGALPrimitiveTopology::GetIndexCount(m_GraphicsPipeline.m_Topology, uiPrimitiveCount); const ezUInt32 uiFirstIndex = ezGALPrimitiveTopology::GetIndexCount(m_GraphicsPipeline.m_Topology, uiFirstPrimitive); if (m_bStereoRendering) { uiInstanceCount *= 2; } if (uiInstanceCount > 1) { if (!m_hIndexBuffer.IsInvalidated()) { return pCommandEncoder->DrawIndexedInstanced(uiIndexCount, uiInstanceCount, uiFirstIndex); } else { return pCommandEncoder->DrawInstanced(uiIndexCount, uiInstanceCount, uiFirstIndex); } } else { if (!m_hIndexBuffer.IsInvalidated()) { return pCommandEncoder->DrawIndexed(uiIndexCount, uiFirstIndex); } else { return pCommandEncoder->Draw(uiIndexCount, uiFirstIndex); } } return EZ_SUCCESS; } ezResult ezRenderContext::Dispatch(ezUInt32 uiThreadGroupCountX, ezUInt32 uiThreadGroupCountY, ezUInt32 uiThreadGroupCountZ) { if (ApplyContextStates().Failed()) { m_Statistics.m_uiFailedDrawcalls++; return EZ_FAILURE; } return GetCommandEncoder()->Dispatch(uiThreadGroupCountX, uiThreadGroupCountY, uiThreadGroupCountZ); } ezResult ezRenderContext::ApplyContextStates(bool bForce) { EZ_ASSERT_DEBUG(m_bRendering || m_bCompute, "Must be either in a rendering or compute scope"); // First apply material state since this can modify all other states. if (bForce || m_StateFlags.IsSet(ezRenderContextFlags::MaterialBindingChanged)) { ApplyMaterialState(); m_StateFlags.Remove(ezRenderContextFlags::MaterialBindingChanged); m_StateFlags.Add(ezRenderContextFlags::BindGroupChanged); } for (ezUInt32 i = 0; i < EZ_GAL_MAX_BIND_GROUPS; ++i) { if (m_BindGroupBuilders[i].IsModified()) { m_StateFlags.Add(ezRenderContextFlags::BindGroupChanged); break; } } bool bRebuildVertexDeclaration = m_StateFlags.IsAnySet(ezRenderContextFlags::ShaderStateChanged | ezRenderContextFlags::MeshBufferBindingChanged); if (bForce || m_StateFlags.IsSet(ezRenderContextFlags::ShaderStateChanged)) { EZ_SUCCEED_OR_RETURN(ApplyShaderState()); m_StateFlags.Remove(ezRenderContextFlags::ShaderStateChanged); } if (m_pActiveGALShader) { const bool bDirty = (bForce || m_StateFlags.IsAnySet(ezRenderContextFlags::BindGroupLayoutChanged | ezRenderContextFlags::BindGroupChanged)); ezLogBlock applyBindingsBlock("Applying Shader Bindings", m_sActiveShader); UploadConstants(); if (bDirty) { const ezUInt32 uiBindGroups = m_pActiveGALShader->GetBindGroupCount(); for (ezUInt32 uiBindGroup = 0; uiBindGroup < uiBindGroups; uiBindGroup++) { const bool bForceBindGroupUpdate = bForce || m_bDirtyBindGroups[uiBindGroup]; const bool bHasMaterialBindGroupResource = uiBindGroup == EZ_GAL_BIND_GROUP_MATERIAL && m_hMaterial.IsValid(); const bool bBindGroupModified = m_BindGroupBuilders[uiBindGroup].IsModified() && !bHasMaterialBindGroupResource; if (bBindGroupModified) m_Statistics.m_uiModifiedBindGroup[uiBindGroup]++; if (bForceBindGroupUpdate || bBindGroupModified) { EZ_SUCCEED_OR_RETURN(ApplyBindGroup(m_pActiveGALShader, uiBindGroup)); } m_bDirtyBindGroups[uiBindGroup] = false; } m_StateFlags.Remove(ezRenderContextFlags::BindGroupLayoutChanged); m_StateFlags.Remove(ezRenderContextFlags::BindGroupChanged); } } if ((bForce || bRebuildVertexDeclaration) && !m_bCompute) { if (m_hActiveGALShader.IsInvalidated()) return EZ_FAILURE; auto pCommandEncoder = GetCommandEncoder(); if (bForce || m_StateFlags.IsSet(ezRenderContextFlags::MeshBufferBindingChanged)) { for (ezUInt32 i = 0; i < EZ_ARRAY_SIZE(m_hVertexBuffers); ++i) { pCommandEncoder->SetVertexBuffer(i, m_hVertexBuffers[i], m_VertexBufferOffsets[i]); } if (!m_hIndexBuffer.IsInvalidated()) pCommandEncoder->SetIndexBuffer(m_hIndexBuffer); } ezGALVertexDeclarationHandle hVertexDeclaration; const bool bHasVertexDeclarations = m_VertexAttributes.GetCount() > 0; if (bHasVertexDeclarations && BuildVertexDeclaration(m_hActiveGALShader, m_VertexBufferStrides, m_VertexBufferBindingRates, m_VertexAttributes, hVertexDeclaration).Failed()) return EZ_FAILURE; // If there is a vertex buffer we need a valid vertex declaration as well. if (hVertexDeclaration.IsInvalidated()) { for (auto hVertexBuffer : m_hVertexBuffers) { if (!hVertexBuffer.IsInvalidated()) { return EZ_FAILURE; } } } m_GraphicsPipeline.m_hVertexDeclaration = hVertexDeclaration; m_StateFlags.Add(ezRenderContextFlags::PipelineChanged); m_StateFlags.Remove(ezRenderContextFlags::MeshBufferBindingChanged); } if (bForce || m_StateFlags.IsSet(ezRenderContextFlags::NonPipelineStateChanged)) { if (m_bUseUserStencilRefValue) { // Set the user provided stencil reference value m_pGALCommandEncoder->SetStencilReference(m_uiUserStencilRefValue); } else { // Set stencil reference value from shader m_pGALCommandEncoder->SetStencilReference(m_uiShaderStencilRefValue); } m_StateFlags.Remove(ezRenderContextFlags::NonPipelineStateChanged); } if (bForce || m_StateFlags.IsSet(ezRenderContextFlags::PipelineChanged)) { m_StateFlags.Remove(ezRenderContextFlags::PipelineChanged); if (m_bRendering) { m_pGALCommandEncoder->SetGraphicsPipeline(ezGALPipelineCache::GetPipeline(m_GraphicsPipeline)); } else if (m_bCompute) { m_pGALCommandEncoder->SetComputePipeline(ezGALPipelineCache::GetPipeline(m_ComputePipeline)); } } if (m_pActiveGALShader) { for (ezUInt32 i = 0; i < m_pActiveGALShader->GetBindGroupCount(); ++i) { const bool bHasMaterialBindGroupResource = i == EZ_GAL_BIND_GROUP_MATERIAL && m_hMaterial.IsValid(); if (!bHasMaterialBindGroupResource) { EZ_ASSERT_DEV(m_pActiveGALShader->GetBindGroupLayout(i) == m_BindGroups[i].m_hBindGroupLayout, "Invalid Bind Group Layout"); } else { EZ_ASSERT_DEV(m_pActiveGALShader->GetBindGroupLayout(i) == m_pMaterialBindGroup->GetDescription().m_hBindGroupLayout, "Invalid Bind Group Resource Layout"); } } } return EZ_SUCCESS; } void ezRenderContext::ResetContextState() { EZ_PROFILE_SCOPE("ezRenderContext::ResetContextState"); m_StateFlags = ezRenderContextFlags::AllStatesInvalid; m_hMaterial.Invalidate(); m_hNewMaterial.Invalidate(); m_pMaterial = nullptr; m_pMaterialBindGroup = nullptr; m_hActiveShader.Invalidate(); m_PermutationVariables.Clear(); m_hActiveShaderPermutation.Invalidate(); m_sActiveShader.Clear(); m_hActiveGALShader.Invalidate(); m_pActiveGALShader = nullptr; static_assert(EZ_ARRAY_SIZE(m_hVertexBuffers) == EZ_GAL_MAX_VERTEX_BUFFER_COUNT); for (ezUInt32 i = 0; i < EZ_ARRAY_SIZE(m_hVertexBuffers); ++i) { m_hVertexBuffers[i].Invalidate(); m_VertexBufferStrides[i] = 0; m_VertexBufferBindingRates[i] = ezGALVertexBindingRate::Vertex; m_VertexBufferOffsets[i] = 0; } m_hIndexBuffer.Invalidate(); m_VertexAttributes.Clear(); m_GraphicsPipeline.m_Topology = ezGALPrimitiveTopology::ENUM_COUNT; // Set to something invalid m_uiMeshBufferPrimitiveCount = 0; ResetBindGroups(); } ezGlobalConstants& ezRenderContext::WriteGlobalConstants() { ezConstantBufferStorage<ezGlobalConstants>* pStorage = nullptr; EZ_VERIFY(TryGetConstantBufferStorage(m_hGlobalConstantBufferStorage, pStorage), "Invalid Global Constant Storage"); return pStorage->GetDataForWriting(); } const ezGlobalConstants& ezRenderContext::ReadGlobalConstants() const { ezConstantBufferStorage<ezGlobalConstants>* pStorage = nullptr; EZ_VERIFY(TryGetConstantBufferStorage(m_hGlobalConstantBufferStorage, pStorage), "Invalid Global Constant Storage"); return pStorage->GetDataForReading(); } void ezRenderContext::SetGlobalAndWorldTimeConstants(ezTime worldTime) { auto& gc = WriteGlobalConstants(); // Wrap around to prevent floating point issues. A wrap around of 1000 allows all frequencies with 3 digits after the decimal. const double fWrapAround = 1000.0; gc.DeltaTime = (float)ezClock::GetGlobalClock()->GetTimeDiff().GetSeconds(); gc.GlobalTime = (float)ezMath::Mod(ezClock::GetGlobalClock()->GetAccumulatedTime().GetSeconds(), fWrapAround); gc.WorldTime = (float)ezMath::Mod(worldTime.GetSeconds(), fWrapAround); } // static ezConstantBufferStorageHandle ezRenderContext::CreateConstantBufferStorage(ezUInt32 uiSizeInBytes, ezConstantBufferStorageBase*& out_pStorage) { EZ_ASSERT_DEV(ezMemoryUtils::IsSizeAligned(uiSizeInBytes, 16u), "Storage struct for constant buffer is not aligned to 16 bytes"); EZ_LOCK(s_ConstantBufferStorageMutex); ezConstantBufferStorageBase* pStorage = nullptr; auto it = s_FreeConstantBufferStorage.Find(uiSizeInBytes); if (it.IsValid()) { ezDynamicArray<ezConstantBufferStorageBase*>& storageForSize = it.Value(); if (!storageForSize.IsEmpty()) { pStorage = storageForSize[0]; storageForSize.RemoveAtAndSwap(0); } } if (pStorage == nullptr) { pStorage = EZ_DEFAULT_NEW(ezConstantBufferStorageBase, uiSizeInBytes); } out_pStorage = pStorage; s_DirtyConstantBuffers.Insert(pStorage); return ezConstantBufferStorageHandle(s_ConstantBufferStorageTable.Insert(pStorage)); } // static void ezRenderContext::DeleteConstantBufferStorage(ezConstantBufferStorageHandle& inout_hStorage) { EZ_LOCK(s_ConstantBufferStorageMutex); ezConstantBufferStorageBase* pStorage = nullptr; if (s_ConstantBufferStorageTable.Remove(inout_hStorage.m_InternalId, &pStorage)) { pStorage->BeforeBeginFrame(); s_DirtyConstantBuffers.Remove(pStorage); ezUInt32 uiSizeInBytes = pStorage->m_Data.GetCount(); auto it = s_FreeConstantBufferStorage.Find(uiSizeInBytes); if (!it.IsValid()) { it = s_FreeConstantBufferStorage.Insert(uiSizeInBytes, ezDynamicArray<ezConstantBufferStorageBase*>()); } it.Value().PushBack(pStorage); } inout_hStorage.Invalidate(); } // static bool ezRenderContext::TryGetConstantBufferStorage(ezConstantBufferStorageHandle hStorage, ezConstantBufferStorageBase*& out_pStorage) { EZ_LOCK(s_ConstantBufferStorageMutex); return s_ConstantBufferStorageTable.TryGetValue(hStorage.m_InternalId, out_pStorage); } void ezRenderContext::MarktConstantBufferStorageModified(ezConstantBufferStorageBase* pDirtyStorage) { EZ_LOCK(s_ConstantBufferStorageMutex); s_DirtyConstantBuffers.Insert(pDirtyStorage); } // static ezGALSamplerStateCreationDescription ezRenderContext::GetDefaultSamplerState(ezBitflags<ezDefaultSamplerFlags> flags) { ezGALSamplerStateCreationDescription desc; desc.m_MinFilter = flags.IsSet(ezDefaultSamplerFlags::LinearFiltering) ? ezGALTextureFilterMode::Linear : ezGALTextureFilterMode::Point; desc.m_MagFilter = flags.IsSet(ezDefaultSamplerFlags::LinearFiltering) ? ezGALTextureFilterMode::Linear : ezGALTextureFilterMode::Point; desc.m_MipFilter = flags.IsSet(ezDefaultSamplerFlags::LinearFiltering) ? ezGALTextureFilterMode::Linear : ezGALTextureFilterMode::Point; desc.m_AddressU = flags.IsSet(ezDefaultSamplerFlags::Clamp) ? ezImageAddressMode::Clamp : ezImageAddressMode::Repeat; desc.m_AddressV = flags.IsSet(ezDefaultSamplerFlags::Clamp) ? ezImageAddressMode::Clamp : ezImageAddressMode::Repeat; desc.m_AddressW = flags.IsSet(ezDefaultSamplerFlags::Clamp) ? ezImageAddressMode::Clamp : ezImageAddressMode::Repeat; return desc; } // private functions ////////////////////////////////////////////////////////////////////////// // static void ezRenderContext::LoadBuiltinShader(ezShaderUtils::ezBuiltinShaderType type, ezShaderUtils::ezBuiltinShader& out_shader) { ezShaderResourceHandle hActiveShader; bool bStereo = false; switch (type) { case ezShaderUtils::ezBuiltinShaderType::CopyImageArray: bStereo = true; [[fallthrough]]; case ezShaderUtils::ezBuiltinShaderType::CopyImage: hActiveShader = ezResourceManager::LoadResource<ezShaderResource>("Shaders/Pipeline/Copy.ezShader"); break; case ezShaderUtils::ezBuiltinShaderType::DownscaleImageArray: bStereo = true; [[fallthrough]]; case ezShaderUtils::ezBuiltinShaderType::DownscaleImage: hActiveShader = ezResourceManager::LoadResource<ezShaderResource>("Shaders/Pipeline/Downscale.ezShader"); break; } EZ_ASSERT_DEV(hActiveShader.IsValid(), "Could not load builtin shader!"); ezHashTable<ezHashedString, ezHashedString> permutationVariables; static ezHashedString sTrue = ezMakeHashedString("TRUE"); static ezHashedString sFalse = ezMakeHashedString("FALSE"); static ezHashedString sCameraMode = ezMakeHashedString("CAMERA_MODE"); static ezHashedString sPerspective = ezMakeHashedString("CAMERA_MODE_PERSPECTIVE"); static ezHashedString sStereo = ezMakeHashedString("CAMERA_MODE_STEREO"); permutationVariables.Insert(sCameraMode, bStereo ? sStereo : sPerspective); EZ_ASSERT_DEV(!bStereo || ezGALDevice::GetDefaultDevice()->GetCapabilities().m_bSupportsVSRenderTargetArrayIndex, "Vertex shader render target index must be supported for stereo rendering."); ezShaderPermutationResourceHandle hActiveShaderPermutation = ezShaderManager::PreloadSinglePermutation(hActiveShader, permutationVariables, false); EZ_ASSERT_DEV(hActiveShaderPermutation.IsValid(), "Could not load builtin shader permutation!"); ezResourceLock<ezShaderPermutationResource> pShaderPermutation(hActiveShaderPermutation, ezResourceAcquireMode::BlockTillLoaded); EZ_ASSERT_DEV(pShaderPermutation->IsShaderValid(), "Builtin shader permutation shader is invalid!"); out_shader.m_hActiveGALShader = pShaderPermutation->GetGALShader(); EZ_ASSERT_DEV(!out_shader.m_hActiveGALShader.IsInvalidated(), "Invalid GAL Shader handle."); out_shader.m_hBlendState = pShaderPermutation->GetBlendState(); out_shader.m_hDepthStencilState = pShaderPermutation->GetDepthStencilState(); out_shader.m_hRasterizerState = pShaderPermutation->GetRasterizerState(); } // static void ezRenderContext::RegisterImmutableSamplers() { ezGALImmutableSamplers::RegisterImmutableSampler(ezMakeHashedString("LinearSampler"), GetDefaultSamplerState(ezDefaultSamplerFlags::LinearFiltering)).AssertSuccess("Failed to register immutable sampler"); ezGALImmutableSamplers::RegisterImmutableSampler(ezMakeHashedString("LinearClampSampler"), GetDefaultSamplerState(ezDefaultSamplerFlags::LinearFiltering | ezDefaultSamplerFlags::Clamp)).AssertSuccess("Failed to register immutable sampler"); ezGALImmutableSamplers::RegisterImmutableSampler(ezMakeHashedString("PointSampler"), GetDefaultSamplerState(ezDefaultSamplerFlags::PointFiltering)).AssertSuccess("Failed to register immutable sampler"); ezGALImmutableSamplers::RegisterImmutableSampler(ezMakeHashedString("PointClampSampler"), GetDefaultSamplerState(ezDefaultSamplerFlags::PointFiltering | ezDefaultSamplerFlags::Clamp)).AssertSuccess("Failed to register immutable sampler"); } // static void ezRenderContext::OnEngineStartup() { ezShaderUtils::g_RequestBuiltinShaderCallback = ezMakeDelegate(ezRenderContext::LoadBuiltinShader); } // static void ezRenderContext::OnEngineShutdown() { ezShaderUtils::g_RequestBuiltinShaderCallback = {}; ezShaderStageBinary::OnEngineShutdown(); for (auto rc : s_Instances) EZ_DEFAULT_DELETE(rc); s_Instances.Clear(); // Cleanup vertex declarations { for (auto it = s_GALVertexDeclarations.GetIterator(); it.IsValid(); ++it) { ezGALDevice::GetDefaultDevice()->DestroyVertexDeclaration(it.Value()); } s_GALVertexDeclarations.Clear(); } // Cleanup constant buffer storage { for (auto it = s_ConstantBufferStorageTable.GetIterator(); it.IsValid(); ++it) { ezConstantBufferStorageBase* pStorage = it.Value(); EZ_DEFAULT_DELETE(pStorage); } s_ConstantBufferStorageTable.Clear(); for (auto it = s_FreeConstantBufferStorage.GetIterator(); it.IsValid(); ++it) { ezDynamicArray<ezConstantBufferStorageBase*>& storageForSize = it.Value(); for (auto& pStorage : storageForSize) { EZ_DEFAULT_DELETE(pStorage); } } s_FreeConstantBufferStorage.Clear(); s_DirtyConstantBuffers.Clear(); } } // static void ezRenderContext::GALStaticDeviceEventHandler(const ezGALDeviceEvent& e) { if (e.m_Type == ezGALDeviceEvent::Type::AfterBeginCommands) { s_pCommandEncoder = e.m_pCommandEncoder; if (s_pDefaultInstance) { s_pDefaultInstance->m_StateFlags = ezRenderContextFlags::AllStatesInvalid; for (ezUInt32 i = 0; i < EZ_GAL_MAX_BIND_GROUPS; ++i) { s_pDefaultInstance->m_bDirtyBindGroups[i] = true; } s_pDefaultInstance->m_pGALCommandEncoder = e.m_pCommandEncoder; // this is executed every frame const ezInt32 iQuality = ezMath::Clamp<ezInt32>(cvar_RenderingTextureQuality, 0, ezGALTextureQuality::Anisotropic16x); s_pDefaultInstance->SetDefaultTextureQuality(static_cast<ezGALTextureQuality::Enum>(iQuality)); } } else if (e.m_Type == ezGALDeviceEvent::Type::BeforeEndCommands) { s_pCommandEncoder = nullptr; if (s_pDefaultInstance) s_pDefaultInstance->m_pGALCommandEncoder = nullptr; } else if (e.m_Type == ezGALDeviceEvent::Type::BeforeBeginFrame) { if (s_pDefaultInstance) { s_pDefaultInstance->ResetContextState(); } EZ_LOCK(s_ConstantBufferStorageMutex); for (auto it = s_ConstantBufferStorageTable.GetIterator(); it.IsValid(); ++it) { it.Value()->BeforeBeginFrame(); s_DirtyConstantBuffers.Insert(it.Value()); } } else if (e.m_Type == ezGALDeviceEvent::Type::BeforeEndFrame) { ezStats::SetStat("RenderContext/BindGroupWrites", ezBindGroupBuilder::s_uiWrites); ezBindGroupBuilder::s_uiWrites = 0; ezStats::SetStat("RenderContext/BindGroupReads", ezBindGroupBuilder::s_uiReads); ezBindGroupBuilder::s_uiReads = 0; if (s_pDefaultInstance) { ezRenderContext::Statistics stats = s_pDefaultInstance->GetAndResetStatistics(); for (ezUInt32 i = 0; i < EZ_GAL_MAX_BIND_GROUPS; ++i) { ezStringBuilder groupName; groupName.SetFormat("RenderContext/BindGroup_{}_Modified", i); ezStats::SetStat(groupName, stats.m_uiModifiedBindGroup[i]); groupName.SetFormat("RenderContext/BindGroup_{}_LayoutChanged", i); ezStats::SetStat(groupName, stats.m_uiLayoutChanged[i]); } } } } // static ezResult ezRenderContext::BuildVertexDeclaration(ezGALShaderHandle hShader, ezArrayPtr<ezUInt32> vertexBufferStrides, ezArrayPtr<ezEnum<ezGALVertexBindingRate>> vertexBufferBindingRates, ezArrayPtr<ezGALVertexAttribute> vertexAttributes, ezGALVertexDeclarationHandle& out_Declaration) { ezInt32 iHighestUsedBinding = -1; for (ezUInt32 i = 0; i < vertexAttributes.GetCount(); ++i) { iHighestUsedBinding = ezMath::Max(iHighestUsedBinding, static_cast<ezInt32>(vertexAttributes[i].m_uiVertexBufferSlot)); } EZ_ASSERT_DEBUG(iHighestUsedBinding < (ezInt32)vertexBufferStrides.GetCount(), "Not enough vertex buffer strides"); EZ_ASSERT_DEBUG(iHighestUsedBinding < (ezInt32)vertexBufferBindingRates.GetCount(), "Not enough vertex buffer binding rates"); ShaderVertexDecl svd; { svd.m_hShader = hShader; ezHashStreamWriter32 writer; writer.WriteBytes(vertexAttributes.GetPtr(), vertexAttributes.ToByteArray().GetCount()).IgnoreResult(); writer.WriteBytes(vertexBufferStrides.GetPtr(), vertexBufferStrides.GetSubArray(0, iHighestUsedBinding + 1).ToByteArray().GetCount()).IgnoreResult(); writer.WriteBytes(vertexBufferBindingRates.GetPtr(), vertexBufferBindingRates.GetSubArray(0, iHighestUsedBinding + 1).ToByteArray().GetCount()).IgnoreResult(); svd.m_uiVertexAttributesHash = writer.GetHashValue(); } bool bExisted = false; auto it = s_GALVertexDeclarations.FindOrAdd(svd, &bExisted); if (!bExisted) { ezGALVertexDeclarationCreationDescription vd; vd.m_hShader = hShader; vd.m_VertexAttributes = vertexAttributes; for (ezInt32 bufferIndex = 0; bufferIndex <= iHighestUsedBinding; ++bufferIndex) { ezGALVertexBinding binding; binding.m_uiStride = vertexBufferStrides[bufferIndex]; binding.m_Rate = vertexBufferBindingRates[bufferIndex]; vd.m_VertexBindings.PushBack(binding); } out_Declaration = ezGALDevice::GetDefaultDevice()->CreateVertexDeclaration(vd); if (out_Declaration.IsInvalidated()) { /* This can happen when the resource system gives you a fallback resource, which then selects a shader that does not fit the mesh layout. E.g. when a material is not yet loaded and the fallback material is used, that fallback material may use another shader, that requires more data streams, than what the mesh provides. This problem will go away, once the proper material is loaded. This can be fixed by ensuring that the fallback material uses a shader that only requires data that is always there, e.g. only position and maybe a texcoord, and of course all meshes must provide at least those data streams. Otherwise, this is harmless, the renderer will ignore invalid drawcalls and once all the correct stuff is available, it will work. */ s_GALVertexDeclarations.Remove(it); ezLog::Warning("Failed to create vertex declaration"); return EZ_FAILURE; } it.Value() = out_Declaration; } out_Declaration = it.Value(); return EZ_SUCCESS; } void ezRenderContext::UploadConstants() { ezBindGroupBuilder& bindGroup = ezRenderContext::GetDefaultInstance()->GetBindGroup(); bindGroup.BindBuffer("ezGlobalConstants", m_hGlobalConstantBufferStorage); EZ_LOCK(s_ConstantBufferStorageMutex); for (auto it = s_DirtyConstantBuffers.GetIterator(); it.IsValid(); ++it) { ezConstantBufferStorageBase* pConstantBufferStorage = it.Key(); pConstantBufferStorage->UploadData(m_pGALCommandEncoder); } s_DirtyConstantBuffers.Clear(); } void ezRenderContext::SetShaderPermutationVariableInternal(const ezHashedString& sName, const ezHashedString& sValue) { ezHashedString* pOldValue = nullptr; m_PermutationVariables.TryGetValue(sName, pOldValue); if (pOldValue == nullptr || *pOldValue != sValue) { m_PermutationVariables.Insert(sName, sValue); m_StateFlags.Add(ezRenderContextFlags::ShaderStateChanged); } } void ezRenderContext::BindShaderInternal(const ezShaderResourceHandle& hShader, ezBitflags<ezShaderBindFlags> flags) { if (flags.IsAnySet(ezShaderBindFlags::ForceRebind) || m_hActiveShader != hShader) { m_ShaderBindFlags = flags; m_hActiveShader = hShader; m_StateFlags.Add(ezRenderContextFlags::ShaderStateChanged); } } ezResult ezRenderContext::ApplyShaderState() { m_hActiveGALShader.Invalidate(); m_StateFlags.Add(ezRenderContextFlags::PipelineChanged); if (!m_hActiveShader.IsValid()) return EZ_FAILURE; m_hActiveShaderPermutation = ezShaderManager::PreloadSinglePermutation(m_hActiveShader, m_PermutationVariables, m_bAllowAsyncShaderLoading); if (!m_hActiveShaderPermutation.IsValid()) return EZ_FAILURE; // Non-material shaders are always force-loaded so we don't accidentally miss to render important passes. const bool bAsyncShaderLoading = m_bAllowAsyncShaderLoading && m_hMaterial.IsValid(); ezResourceLock<ezShaderPermutationResource> pShaderPermutation(m_hActiveShaderPermutation, bAsyncShaderLoading ? ezResourceAcquireMode::AllowLoadingFallback : ezResourceAcquireMode::BlockTillLoaded); if (!pShaderPermutation.IsValid() || !pShaderPermutation->IsShaderValid()) return EZ_FAILURE; m_sActiveShader = pShaderPermutation->GetResourceDescription(); m_hActiveGALShader = pShaderPermutation->GetGALShader(); m_GraphicsPipeline.m_hShader = m_hActiveGALShader; m_ComputePipeline.m_hShader = m_hActiveGALShader; EZ_ASSERT_DEV(!m_hActiveGALShader.IsInvalidated(), "Invalid GAL Shader handle."); m_pActiveGALShader = ezGALDevice::GetDefaultDevice()->GetShader(m_hActiveGALShader); EZ_ASSERT_DEV(m_pActiveGALShader, "Invalid GAL Shader handle."); const ezUInt32 uiBindGroups = m_pActiveGALShader->GetBindGroupCount(); // On DX11 or other platforms that do not support multiple bind groups, we always need to invalidate all bind groups as another bind group could have overwritten the state of a different one as they all write to the same state. // E.g. a shader that has only one bind group, can overwrite everything. If we then switch to a shader that uses 4 bind groups, while bind groups 1 to 3 have not been touched, they are still dirty as the old 1 BG layout and the new 4 BG layout can overlap outside of BG0. const bool bForceAllBindGroupsDirty = !ezGALDevice::GetDefaultDevice()->GetCapabilities().m_bSupportsMultipleBindGroups; for (ezUInt32 i = 0; i < uiBindGroups; ++i) { if (bForceAllBindGroupsDirty || m_pActiveGALShader->GetBindGroupLayout(i) != m_BindGroups[i].m_hBindGroupLayout) { m_Statistics.m_uiLayoutChanged[i]++; m_bDirtyBindGroups[i] = true; m_StateFlags.Add(ezRenderContextFlags::BindGroupLayoutChanged); } } // Set render state from shader if (!m_bCompute) { if (!m_ShaderBindFlags.IsSet(ezShaderBindFlags::NoBlendState)) m_GraphicsPipeline.m_hBlendState = pShaderPermutation->GetBlendState(); if (!m_ShaderBindFlags.IsSet(ezShaderBindFlags::NoRasterizerState)) m_GraphicsPipeline.m_hRasterizerState = pShaderPermutation->GetRasterizerState(); if (!m_ShaderBindFlags.IsSet(ezShaderBindFlags::NoDepthStencilState)) { m_GraphicsPipeline.m_hDepthStencilState = pShaderPermutation->GetDepthStencilState(); m_uiShaderStencilRefValue = pShaderPermutation->GetShaderStencilRefValue(); m_bUseUserStencilRefValue = pShaderPermutation->GetUseUserStencilRefValue(); } else { m_bUseUserStencilRefValue = true; } m_StateFlags.Add(ezRenderContextFlags::NonPipelineStateChanged); } return EZ_SUCCESS; } void ezRenderContext::ApplyMaterialState() { if (!m_hNewMaterial.IsValid()) { BindShaderInternal(ezShaderResourceHandle(), ezShaderBindFlags::Default); m_pMaterial = nullptr; m_pMaterialBindGroup = nullptr; return; } if (m_hNewMaterial != m_hMaterial) { // check whether material has been modified ezResourceLock<ezMaterialResource> pMaterial(m_hNewMaterial, ezResourceAcquireMode::AllowLoadingFallback); const ezMaterialManager::MaterialData* data = ezMaterialManager::GetMaterialData(pMaterial.GetPointer()); if (data == nullptr) { BindShaderInternal(ezShaderResourceHandle(), ezShaderBindFlags::Default); return; } BindShaderInternal(data->m_hShader, ezShaderBindFlags::Default); for (const ezPermutationVar& perm : data->m_PermutationVars) { SetShaderPermutationVariableInternal(perm.m_sName, perm.m_sValue); } m_hMaterial = m_hNewMaterial; // We don't know the permutation to use yet and thus also not the correct bind group layout. Therefore, we store the raw address of the material to be able to look up the correct bind group in ApplyBindGroup. We can't acquire the resource lock again as that might result in a different address (fallback vs real) and we would mismatch the material data and bind group from two different materials. m_pMaterial = pMaterial.GetPointer(); } } ezResult ezRenderContext::ApplyBindGroup(const ezGALShader* pShader, ezUInt32 uiBindGroup) { ezGALBindGroupLayoutHandle hLayout = pShader->GetBindGroupLayout(uiBindGroup); if (hLayout.IsInvalidated()) return EZ_FAILURE; const bool bHasMaterialBindGroupResource = uiBindGroup == EZ_GAL_BIND_GROUP_MATERIAL && m_hMaterial.IsValid(); if (bHasMaterialBindGroupResource) { ezGALBindGroupHandle hBindGroup = ezMaterialManager::GetMaterialBindGroup(m_pMaterial, hLayout); if (hBindGroup.IsInvalidated()) return EZ_FAILURE; m_pMaterialBindGroup = m_pGALCommandEncoder->GetDevice().GetBindGroup(hBindGroup); m_pGALCommandEncoder->SetBindGroup(uiBindGroup, hBindGroup); return EZ_SUCCESS; } ezBitflags<ezGALBindGroupItemFlags> metaFlags; m_BindGroupBuilders[uiBindGroup].CreateBindGroup(hLayout, m_BindGroups[uiBindGroup], metaFlags); m_pGALCommandEncoder->SetBindGroup(uiBindGroup, m_BindGroups[uiBindGroup]); return EZ_SUCCESS; } void ezRenderContext::SetDefaultTextureQuality(ezGALTextureQuality::Enum quality, bool bForce) { if (!bForce && m_DefaultTextureQuality == quality) return; m_DefaultTextureQuality = quality; cvar_RenderingTextureQuality = static_cast<ezInt32>(quality); if (m_pGALCommandEncoder) { switch (m_DefaultTextureQuality) { case ezGALTextureQuality::Nearest: m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::LowestQuality, ezGALTextureQuality::Nearest); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::LowQuality, ezGALTextureQuality::Nearest); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::DefaultQuality, ezGALTextureQuality::Nearest); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::HighQuality, ezGALTextureQuality::Nearest); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::HighestQuality, ezGALTextureQuality::Nearest); break; case ezGALTextureQuality::Bilinear: m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::LowestQuality, ezGALTextureQuality::Bilinear); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::LowQuality, ezGALTextureQuality::Bilinear); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::DefaultQuality, ezGALTextureQuality::Bilinear); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::HighQuality, ezGALTextureQuality::Trilinear); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::HighestQuality, ezGALTextureQuality::Anisotropic2x); break; case ezGALTextureQuality::Trilinear: m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::LowestQuality, ezGALTextureQuality::Bilinear); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::LowQuality, ezGALTextureQuality::Bilinear); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::DefaultQuality, ezGALTextureQuality::Trilinear); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::HighQuality, ezGALTextureQuality::Anisotropic2x); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::HighestQuality, ezGALTextureQuality::Anisotropic4x); break; case ezGALTextureQuality::Anisotropic2x: m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::LowestQuality, ezGALTextureQuality::Bilinear); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::LowQuality, ezGALTextureQuality::Trilinear); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::DefaultQuality, ezGALTextureQuality::Anisotropic2x); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::HighQuality, ezGALTextureQuality::Anisotropic4x); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::HighestQuality, ezGALTextureQuality::Anisotropic8x); break; case ezGALTextureQuality::Anisotropic4x: m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::LowestQuality, ezGALTextureQuality::Trilinear); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::LowQuality, ezGALTextureQuality::Anisotropic2x); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::DefaultQuality, ezGALTextureQuality::Anisotropic4x); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::HighQuality, ezGALTextureQuality::Anisotropic8x); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::HighestQuality, ezGALTextureQuality::Anisotropic16x); break; case ezGALTextureQuality::Anisotropic8x: m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::LowestQuality, ezGALTextureQuality::Anisotropic2x); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::LowQuality, ezGALTextureQuality::Anisotropic4x); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::DefaultQuality, ezGALTextureQuality::Anisotropic8x); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::HighQuality, ezGALTextureQuality::Anisotropic16x); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::HighestQuality, ezGALTextureQuality::Anisotropic16x); break; case ezGALTextureQuality::Anisotropic16x: m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::LowestQuality, ezGALTextureQuality::Anisotropic4x); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::LowQuality, ezGALTextureQuality::Anisotropic8x); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::DefaultQuality, ezGALTextureQuality::Anisotropic16x); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::HighQuality, ezGALTextureQuality::Anisotropic16x); m_pGALCommandEncoder->GetDevice().SetTextureQualityMode(ezGALTextureQualitySlot::HighestQuality, ezGALTextureQuality::Anisotropic16x); break; } m_pGALCommandEncoder->GetDevice().UpdateTextureQuality(); } } void ezRenderContext::SetAllowAsyncShaderLoading(bool bAllow) { m_bAllowAsyncShaderLoading = bAllow; } bool ezRenderContext::GetAllowAsyncShaderLoading() { return m_bAllowAsyncShaderLoading; } EZ_STATICLINK_FILE(RendererCore, RendererCore_RenderContext_Implementation_RenderContext);