#include "TemporalHistoryBuffers.h" #include "GlRenderConstants.h" #include "ShaderTypes.h" #include #include #include bool TemporalHistoryBuffers::ValidateTextureUnitBudget(const std::vector& layerStates, unsigned historyCap, std::string& error) const { unsigned requiredUnits = kSourceHistoryTextureUnitBase; for (const RuntimeRenderState& state : layerStates) { unsigned textTextureCount = 0; for (const ShaderParameterDefinition& definition : state.parameterDefinitions) { if (definition.type == ShaderParameterType::Text) ++textTextureCount; } const unsigned totalShaderTextures = static_cast(state.textureAssets.size()) + textTextureCount; const unsigned feedbackTextureCount = state.feedback.enabled ? 1u : 0u; const unsigned layerRequiredUnits = kSourceHistoryTextureUnitBase + (state.isTemporal ? historyCap + historyCap : 0u) + feedbackTextureCount + totalShaderTextures; if (layerRequiredUnits > requiredUnits) requiredUnits = layerRequiredUnits; } GLint maxTextureUnits = 0; glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits); const unsigned availableUnits = maxTextureUnits > 0 ? static_cast(maxTextureUnits) : 0u; if (requiredUnits > availableUnits) { std::ostringstream message; message << "The current history and shader texture asset configuration requires " << requiredUnits << " fragment texture units, but only " << maxTextureUnits << " are available."; error = message.str(); return false; } return true; } bool TemporalHistoryBuffers::EnsureResources(const std::vector& layerStates, unsigned historyCap, unsigned frameWidth, unsigned frameHeight, std::string& error) { const bool sourceHistoryNeeded = std::any_of(layerStates.begin(), layerStates.end(), [](const RuntimeRenderState& state) { return state.isTemporal && state.effectiveTemporalHistoryLength > 0; }); const unsigned sourceHistoryLength = sourceHistoryNeeded ? historyCap : 0; if (sourceHistoryRing.effectiveLength != sourceHistoryLength) { if (!CreateRing(sourceHistoryRing, sourceHistoryLength, TemporalHistorySource::Source, frameWidth, frameHeight, error)) return false; mNeedsReset = true; } std::set requiredPreLayerIds; for (const RuntimeRenderState& state : layerStates) { if (!state.isTemporal || state.temporalHistorySource != TemporalHistorySource::PreLayerInput) continue; requiredPreLayerIds.insert(state.layerId); auto historyIt = preLayerHistoryByLayerId.find(state.layerId); if (historyIt == preLayerHistoryByLayerId.end() || historyIt->second.effectiveLength != state.effectiveTemporalHistoryLength) { Ring replacement; if (!CreateRing(replacement, state.effectiveTemporalHistoryLength, TemporalHistorySource::PreLayerInput, frameWidth, frameHeight, error)) return false; preLayerHistoryByLayerId[state.layerId] = std::move(replacement); mNeedsReset = true; } } for (auto it = preLayerHistoryByLayerId.begin(); it != preLayerHistoryByLayerId.end();) { if (requiredPreLayerIds.find(it->first) == requiredPreLayerIds.end()) { DestroyRing(it->second); it = preLayerHistoryByLayerId.erase(it); mNeedsReset = true; } else { ++it; } } if (mNeedsReset) ResetState(); return true; } bool TemporalHistoryBuffers::CreateRing(Ring& ring, unsigned effectiveLength, TemporalHistorySource historySource, unsigned frameWidth, unsigned frameHeight, std::string& error) { DestroyRing(ring); ring.effectiveLength = effectiveLength; ring.historySource = historySource; if (effectiveLength == 0) return true; ring.slots.resize(effectiveLength); for (Slot& slot : ring.slots) { glGenTextures(1, &slot.texture); glBindTexture(GL_TEXTURE_2D, slot.texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, frameWidth, frameHeight, 0, GL_RGBA, GL_FLOAT, NULL); glGenFramebuffers(1, &slot.framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, slot.framebuffer); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, slot.texture, 0); const GLenum framebufferStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (framebufferStatus != GL_FRAMEBUFFER_COMPLETE) { error = "Failed to initialize a temporal history framebuffer."; glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindTexture(GL_TEXTURE_2D, 0); DestroyRing(ring); return false; } } glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindTexture(GL_TEXTURE_2D, 0); return true; } void TemporalHistoryBuffers::DestroyRing(Ring& ring) { for (Slot& slot : ring.slots) { if (slot.framebuffer != 0) glDeleteFramebuffers(1, &slot.framebuffer); if (slot.texture != 0) glDeleteTextures(1, &slot.texture); slot.framebuffer = 0; slot.texture = 0; } ring.slots.clear(); ring.nextWriteIndex = 0; ring.filledCount = 0; ring.effectiveLength = 0; ring.historySource = TemporalHistorySource::None; } void TemporalHistoryBuffers::DestroyResources() { DestroyRing(sourceHistoryRing); for (auto& historyEntry : preLayerHistoryByLayerId) DestroyRing(historyEntry.second); preLayerHistoryByLayerId.clear(); mNeedsReset = true; } void TemporalHistoryBuffers::ResetState() { sourceHistoryRing.nextWriteIndex = 0; sourceHistoryRing.filledCount = 0; for (auto& historyEntry : preLayerHistoryByLayerId) { historyEntry.second.nextWriteIndex = 0; historyEntry.second.filledCount = 0; } mNeedsReset = false; } void TemporalHistoryBuffers::PushFramebuffer(GLuint sourceFramebuffer, Ring& ring, unsigned frameWidth, unsigned frameHeight) { if (ring.effectiveLength == 0 || ring.slots.empty()) return; Slot& targetSlot = ring.slots[ring.nextWriteIndex]; glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceFramebuffer); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, targetSlot.framebuffer); glBlitFramebuffer(0, 0, frameWidth, frameHeight, 0, 0, frameWidth, frameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR); ring.nextWriteIndex = (ring.nextWriteIndex + 1) % ring.slots.size(); ring.filledCount = std::min(ring.filledCount + 1, ring.slots.size()); } void TemporalHistoryBuffers::PushSourceFramebuffer(GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight) { PushFramebuffer(sourceFramebuffer, sourceHistoryRing, frameWidth, frameHeight); } void TemporalHistoryBuffers::PushPreLayerFramebuffer(const std::string& layerId, GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight) { auto historyIt = preLayerHistoryByLayerId.find(layerId); if (historyIt != preLayerHistoryByLayerId.end()) PushFramebuffer(sourceFramebuffer, historyIt->second, frameWidth, frameHeight); } void TemporalHistoryBuffers::BindSamplers(const RuntimeRenderState& state, GLuint currentSourceTexture, unsigned historyCap) { for (unsigned index = 0; index < historyCap; ++index) { glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + index); glBindTexture(GL_TEXTURE_2D, ResolveTexture(sourceHistoryRing, currentSourceTexture, index)); } const GLuint temporalBase = kSourceHistoryTextureUnitBase + historyCap; const Ring* temporalRing = nullptr; auto it = preLayerHistoryByLayerId.find(state.layerId); if (it != preLayerHistoryByLayerId.end()) temporalRing = &it->second; for (unsigned index = 0; index < historyCap; ++index) { glActiveTexture(GL_TEXTURE0 + temporalBase + index); glBindTexture(GL_TEXTURE_2D, temporalRing ? ResolveTexture(*temporalRing, currentSourceTexture, index) : currentSourceTexture); } glActiveTexture(GL_TEXTURE0); } std::vector TemporalHistoryBuffers::ResolveSourceHistoryTextures(GLuint fallbackTexture, unsigned historyCap) const { std::vector textures; textures.reserve(historyCap); for (unsigned index = 0; index < historyCap; ++index) textures.push_back(ResolveTexture(sourceHistoryRing, fallbackTexture, index)); return textures; } std::vector TemporalHistoryBuffers::ResolveTemporalHistoryTextures(const RuntimeRenderState& state, GLuint fallbackTexture, unsigned historyCap) const { const Ring* temporalRing = nullptr; auto it = preLayerHistoryByLayerId.find(state.layerId); if (it != preLayerHistoryByLayerId.end()) temporalRing = &it->second; std::vector textures; textures.reserve(historyCap); for (unsigned index = 0; index < historyCap; ++index) textures.push_back(temporalRing ? ResolveTexture(*temporalRing, fallbackTexture, index) : fallbackTexture); return textures; } GLuint TemporalHistoryBuffers::ResolveTexture(const Ring& ring, GLuint fallbackTexture, std::size_t framesAgo) const { if (ring.filledCount == 0 || ring.slots.empty()) return fallbackTexture; const std::size_t clampedOffset = std::min(framesAgo, ring.filledCount - 1); const std::size_t newestIndex = (ring.nextWriteIndex + ring.slots.size() - 1) % ring.slots.size(); const std::size_t slotIndex = (newestIndex + ring.slots.size() - clampedOffset) % ring.slots.size(); return ring.slots[slotIndex].texture != 0 ? ring.slots[slotIndex].texture : fallbackTexture; } unsigned TemporalHistoryBuffers::SourceAvailableCount() const { return static_cast(sourceHistoryRing.filledCount); } unsigned TemporalHistoryBuffers::AvailableCountForLayer(const std::string& layerId) const { auto it = preLayerHistoryByLayerId.find(layerId); if (it == preLayerHistoryByLayerId.end()) return 0; return static_cast(it->second.filledCount); }