From 17df8fafa4aa6b8e3eb7b511126afa5e287928c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Leclercq?= Date: Fri, 3 Dec 2021 22:15:34 +0100 Subject: [PATCH] Renderer/CommandBuffer: Add support for texture blit/copy --- .../OpenGLRenderer/OpenGLCommandBuffer.hpp | 21 +++++ .../OpenGLRenderer/OpenGLCommandBuffer.inl | 25 ++++++ .../OpenGLCommandBufferBuilder.hpp | 3 + .../Nazara/OpenGLRenderer/Wrapper/Context.hpp | 2 +- .../Nazara/Renderer/CommandBufferBuilder.hpp | 5 ++ .../VulkanCommandBufferBuilder.hpp | 3 + .../VulkanRenderer/Wrapper/CommandBuffer.hpp | 3 + .../VulkanRenderer/Wrapper/CommandBuffer.inl | 10 +++ .../OpenGLRenderer/OpenGLCommandBuffer.cpp | 8 ++ .../OpenGLCommandBufferBuilder.cpp | 17 ++++ src/Nazara/OpenGLRenderer/Wrapper/Context.cpp | 8 +- .../VulkanCommandBufferBuilder.cpp | 78 +++++++++++++++++++ 12 files changed, 178 insertions(+), 5 deletions(-) diff --git a/include/Nazara/OpenGLRenderer/OpenGLCommandBuffer.hpp b/include/Nazara/OpenGLRenderer/OpenGLCommandBuffer.hpp index e40d9a49f..2cd8a35ba 100644 --- a/include/Nazara/OpenGLRenderer/OpenGLCommandBuffer.hpp +++ b/include/Nazara/OpenGLRenderer/OpenGLCommandBuffer.hpp @@ -41,9 +41,11 @@ namespace Nz inline void BindPipeline(const OpenGLRenderPipeline* pipeline); inline void BindShaderBinding(const OpenGLRenderPipelineLayout& pipelineLayout, UInt32 set, const OpenGLShaderBinding* binding); inline void BindVertexBuffer(UInt32 binding, GLuint vertexBuffer, UInt64 offset = 0); + inline void BlitTexture(const GL::Texture& source, const Boxui& sourceBox, const GL::Texture& target, const Boxui& targetBox, SamplerFilter filter = SamplerFilter::Nearest); inline void CopyBuffer(GLuint source, GLuint target, UInt64 size, UInt64 sourceOffset = 0, UInt64 targetOffset = 0); inline void CopyBuffer(const UploadPool::Allocation& allocation, GLuint target, UInt64 size, UInt64 sourceOffset = 0, UInt64 targetOffset = 0); + inline void CopyTexture(const GL::Texture& source, const Boxui& sourceBox, const GL::Texture& target, const Vector3ui& targetPoint); inline void Draw(UInt32 vertexCount, UInt32 instanceCount = 1, UInt32 firstVertex = 0, UInt32 firstInstance = 0); inline void DrawIndexed(UInt32 indexCount, UInt32 instanceCount = 1, UInt32 firstIndex = 0, UInt32 firstInstance = 0); @@ -75,6 +77,15 @@ namespace Nz Color color; }; + struct BlitTextureData + { + const GL::Texture* source; + const GL::Texture* target; + Boxui sourceBox; + Boxui targetBox; + SamplerFilter filter; + }; + struct CopyBufferData { GLuint source; @@ -84,6 +95,14 @@ namespace Nz UInt64 targetOffset; }; + struct CopyTextureData + { + const GL::Texture* source; + const GL::Texture* target; + Boxui sourceBox; + Vector3ui targetPoint; + }; + struct CopyBufferFromMemoryData { const void* memory; @@ -141,8 +160,10 @@ namespace Nz using CommandData = std::variant< BeginDebugRegionData, + BlitTextureData, CopyBufferData, CopyBufferFromMemoryData, + CopyTextureData, DrawData, DrawIndexedData, EndDebugRegionData, diff --git a/include/Nazara/OpenGLRenderer/OpenGLCommandBuffer.inl b/include/Nazara/OpenGLRenderer/OpenGLCommandBuffer.inl index 1640ca1ed..913ff272d 100644 --- a/include/Nazara/OpenGLRenderer/OpenGLCommandBuffer.inl +++ b/include/Nazara/OpenGLRenderer/OpenGLCommandBuffer.inl @@ -62,6 +62,19 @@ namespace Nz vertexBufferData.vertexBuffer = vertexBuffer; } + inline void OpenGLCommandBuffer::BlitTexture(const GL::Texture& source, const Boxui& sourceBox, const GL::Texture& target, const Boxui& targetBox, SamplerFilter filter) + { + BlitTextureData blitTexture = { + &source, + &target, + sourceBox, + targetBox, + filter + }; + + m_commands.emplace_back(std::move(blitTexture)); + } + inline void OpenGLCommandBuffer::CopyBuffer(GLuint source, GLuint target, UInt64 size, UInt64 sourceOffset, UInt64 targetOffset) { CopyBufferData copyBuffer = { @@ -87,6 +100,18 @@ namespace Nz m_commands.emplace_back(std::move(copyBuffer)); } + inline void OpenGLCommandBuffer::CopyTexture(const GL::Texture& source, const Boxui& sourceBox, const GL::Texture& target, const Vector3ui& targetPoint) + { + CopyTextureData copyTexture = { + &source, + &target, + sourceBox, + targetPoint + }; + + m_commands.emplace_back(std::move(copyTexture)); + } + inline void OpenGLCommandBuffer::Draw(UInt32 vertexCount, UInt32 instanceCount, UInt32 firstVertex, UInt32 firstInstance) { if (!m_currentStates.pipeline) diff --git a/include/Nazara/OpenGLRenderer/OpenGLCommandBufferBuilder.hpp b/include/Nazara/OpenGLRenderer/OpenGLCommandBufferBuilder.hpp index 034a34ad8..6aa7d7706 100644 --- a/include/Nazara/OpenGLRenderer/OpenGLCommandBufferBuilder.hpp +++ b/include/Nazara/OpenGLRenderer/OpenGLCommandBufferBuilder.hpp @@ -32,8 +32,11 @@ namespace Nz void BindShaderBinding(const RenderPipelineLayout& pipelineLayout, UInt32 set, const ShaderBinding& binding) override; void BindVertexBuffer(UInt32 binding, const AbstractBuffer& vertexBuffer, UInt64 offset = 0) override; + void BlitTexture(const Texture& fromTexture, const Boxui& fromBox, TextureLayout fromLayout, const Texture& toTexture, const Boxui& toBox, TextureLayout toLayout, SamplerFilter filter) override; + void CopyBuffer(const RenderBufferView& source, const RenderBufferView& target, UInt64 size, UInt64 sourceOffset = 0, UInt64 targetOffset = 0) override; void CopyBuffer(const UploadPool::Allocation& allocation, const RenderBufferView& target, UInt64 size, UInt64 sourceOffset = 0, UInt64 targetOffset = 0) override; + void CopyTexture(const Texture& fromTexture, const Boxui& fromBox, TextureLayout fromLayout, const Texture& toTexture, const Vector3ui& toPos, TextureLayout toLayout) override; void Draw(UInt32 vertexCount, UInt32 instanceCount = 1, UInt32 firstVertex = 0, UInt32 firstInstance = 0) override; void DrawIndexed(UInt32 indexCount, UInt32 instanceCount = 1, UInt32 firstIndex = 0, UInt32 firstInstance = 0) override; diff --git a/include/Nazara/OpenGLRenderer/Wrapper/Context.hpp b/include/Nazara/OpenGLRenderer/Wrapper/Context.hpp index a592275ab..6bfab4b90 100644 --- a/include/Nazara/OpenGLRenderer/Wrapper/Context.hpp +++ b/include/Nazara/OpenGLRenderer/Wrapper/Context.hpp @@ -125,7 +125,7 @@ namespace Nz::GL void BindUniformBuffer(UInt32 uboUnit, GLuint buffer, GLintptr offset, GLsizeiptr size) const; void BindVertexArray(GLuint vertexArray, bool force = false) const; - bool BlitTexture(const Texture& source, const Texture& destination, const Boxui& srcBox, const Vector3ui& dstPos, SamplerFilter filter) const; + bool BlitTexture(const Texture& source, const Texture& destination, const Boxui& srcBox, const Boxui& dstBox, SamplerFilter filter) const; bool ClearErrorStack() const; diff --git a/include/Nazara/Renderer/CommandBufferBuilder.hpp b/include/Nazara/Renderer/CommandBufferBuilder.hpp index 6ffc724c1..b1bea66d0 100644 --- a/include/Nazara/Renderer/CommandBufferBuilder.hpp +++ b/include/Nazara/Renderer/CommandBufferBuilder.hpp @@ -9,11 +9,13 @@ #include #include +#include #include #include #include #include #include +#include #include namespace Nz @@ -46,10 +48,13 @@ namespace Nz virtual void BindShaderBinding(const RenderPipelineLayout& pipelineLayout, UInt32 set, const ShaderBinding& binding) = 0; virtual void BindVertexBuffer(UInt32 binding, const AbstractBuffer& vertexBuffer, UInt64 offset = 0) = 0; + virtual void BlitTexture(const Texture& fromTexture, const Boxui& fromBox, TextureLayout fromLayout, const Texture& toTexture, const Boxui& toBox, TextureLayout toLayout, SamplerFilter filter) = 0; + inline void CopyBuffer(const RenderBufferView& source, const RenderBufferView& target); virtual void CopyBuffer(const RenderBufferView& source, const RenderBufferView& target, UInt64 size, UInt64 fromOffset = 0, UInt64 toOffset = 0) = 0; inline void CopyBuffer(const UploadPool::Allocation& allocation, const RenderBufferView& target); virtual void CopyBuffer(const UploadPool::Allocation& allocation, const RenderBufferView& target, UInt64 size, UInt64 fromOffset = 0, UInt64 toOffset = 0) = 0; + virtual void CopyTexture(const Texture& fromTexture, const Boxui& fromBox, TextureLayout fromLayout, const Texture& toTexture, const Vector3ui& toPos, TextureLayout toLayout) = 0; virtual void Draw(UInt32 vertexCount, UInt32 instanceCount = 1, UInt32 firstVertex = 0, UInt32 firstInstance = 0) = 0; virtual void DrawIndexed(UInt32 indexCount, UInt32 instanceCount = 1, UInt32 firstIndex = 0, UInt32 firstInstance = 0) = 0; diff --git a/include/Nazara/VulkanRenderer/VulkanCommandBufferBuilder.hpp b/include/Nazara/VulkanRenderer/VulkanCommandBufferBuilder.hpp index 18775d77b..cdf99e87d 100644 --- a/include/Nazara/VulkanRenderer/VulkanCommandBufferBuilder.hpp +++ b/include/Nazara/VulkanRenderer/VulkanCommandBufferBuilder.hpp @@ -33,8 +33,11 @@ namespace Nz void BindShaderBinding(const RenderPipelineLayout& pipelineLayout, UInt32 set, const ShaderBinding& binding) override; void BindVertexBuffer(UInt32 binding, const AbstractBuffer& vertexBuffer, UInt64 offset = 0) override; + void BlitTexture(const Texture& fromTexture, const Boxui& fromBox, TextureLayout fromLayout, const Texture& toTexture, const Boxui& toBox, TextureLayout toLayout, SamplerFilter filter) override; + void CopyBuffer(const RenderBufferView& source, const RenderBufferView& target, UInt64 size, UInt64 sourceOffset = 0, UInt64 targetOffset = 0) override; void CopyBuffer(const UploadPool::Allocation& allocation, const RenderBufferView& target, UInt64 size, UInt64 sourceOffset = 0, UInt64 targetOffset = 0) override; + void CopyTexture(const Texture& fromTexture, const Boxui& fromBox, TextureLayout fromLayout, const Texture& toTexture, const Vector3ui& toPos, TextureLayout toLayout) override; void Draw(UInt32 vertexCount, UInt32 instanceCount = 1, UInt32 firstVertex = 0, UInt32 firstInstance = 0) override; void DrawIndexed(UInt32 indexCount, UInt32 instanceCount = 1, UInt32 firstIndex = 0, UInt32 firstInstance = 0) override; diff --git a/include/Nazara/VulkanRenderer/Wrapper/CommandBuffer.hpp b/include/Nazara/VulkanRenderer/Wrapper/CommandBuffer.hpp index f99b135e6..bb5d54798 100644 --- a/include/Nazara/VulkanRenderer/Wrapper/CommandBuffer.hpp +++ b/include/Nazara/VulkanRenderer/Wrapper/CommandBuffer.hpp @@ -46,6 +46,9 @@ namespace Nz inline void BindVertexBuffer(UInt32 binding, const VkBuffer buffer, const VkDeviceSize offset); inline void BindVertexBuffers(UInt32 firstBinding, UInt32 bindingCount, const VkBuffer* buffer, const VkDeviceSize* offset); + inline void BlitImage(VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, const VkImageBlit& region, VkFilter filter); + inline void BlitImage(VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, UInt32 regionCount, const VkImageBlit* regions, VkFilter filter); + inline void ClearAttachment(const VkClearAttachment& attachment, const VkClearRect& rect); inline void ClearAttachments(UInt32 attachmentCount, const VkClearAttachment* attachments, UInt32 rectCount, const VkClearRect* rects); diff --git a/include/Nazara/VulkanRenderer/Wrapper/CommandBuffer.inl b/include/Nazara/VulkanRenderer/Wrapper/CommandBuffer.inl index 31da02182..461b69084 100644 --- a/include/Nazara/VulkanRenderer/Wrapper/CommandBuffer.inl +++ b/include/Nazara/VulkanRenderer/Wrapper/CommandBuffer.inl @@ -184,6 +184,16 @@ namespace Nz return m_pool->GetDevice()->vkCmdBindVertexBuffers(m_handle, firstBinding, bindingCount, buffer, offset); } + inline void CommandBuffer::BlitImage(VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, const VkImageBlit& region, VkFilter filter) + { + return BlitImage(srcImage, srcImageLayout, dstImage, dstImageLayout, 1U, ®ion, filter); + } + + inline void CommandBuffer::BlitImage(VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, UInt32 regionCount, const VkImageBlit* regions, VkFilter filter) + { + return m_pool->GetDevice()->vkCmdBlitImage(m_handle, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, regions, filter); + } + inline void CommandBuffer::ClearAttachment(const VkClearAttachment& attachment, const VkClearRect& rect) { return ClearAttachments(1U, &attachment, 1U, &rect); diff --git a/src/Nazara/OpenGLRenderer/OpenGLCommandBuffer.cpp b/src/Nazara/OpenGLRenderer/OpenGLCommandBuffer.cpp index 09b6c9d3b..e9f96242b 100644 --- a/src/Nazara/OpenGLRenderer/OpenGLCommandBuffer.cpp +++ b/src/Nazara/OpenGLRenderer/OpenGLCommandBuffer.cpp @@ -78,6 +78,10 @@ namespace Nz if (context->glPushDebugGroup) context->glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, GLsizei(command.regionName.size()), command.regionName.data()); } + else if constexpr (std::is_same_v) + { + context->BlitTexture(*command.source, *command.target, command.sourceBox, command.targetBox, command.filter); + } else if constexpr (std::is_same_v) { context->BindBuffer(GL::BufferTarget::CopyRead, command.source); @@ -89,6 +93,10 @@ namespace Nz context->BindBuffer(GL::BufferTarget::CopyWrite, command.target); context->glBufferSubData(GL_COPY_WRITE_BUFFER, command.targetOffset, command.size, command.memory); } + else if constexpr (std::is_same_v) + { + context->CopyTexture(*command.source, *command.target, command.sourceBox, command.targetPoint); + } else if constexpr (std::is_same_v) { ApplyStates(*context, command.states); diff --git a/src/Nazara/OpenGLRenderer/OpenGLCommandBufferBuilder.cpp b/src/Nazara/OpenGLRenderer/OpenGLCommandBufferBuilder.cpp index b908879d8..a43127e82 100644 --- a/src/Nazara/OpenGLRenderer/OpenGLCommandBufferBuilder.cpp +++ b/src/Nazara/OpenGLRenderer/OpenGLCommandBufferBuilder.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -60,6 +61,14 @@ namespace Nz m_commandBuffer.BindVertexBuffer(binding, glBuffer.GetBuffer().GetObjectId(), offset); } + void OpenGLCommandBufferBuilder::BlitTexture(const Texture& fromTexture, const Boxui& fromBox, TextureLayout /*fromLayout*/, const Texture& toTexture, const Boxui& toBox, TextureLayout /*toLayout*/, SamplerFilter filter) + { + const OpenGLTexture& sourceTexture = static_cast(fromTexture); + const OpenGLTexture& targetTexture = static_cast(toTexture); + + m_commandBuffer.BlitTexture(sourceTexture.GetTexture(), fromBox, targetTexture.GetTexture(), toBox, filter); + } + void OpenGLCommandBufferBuilder::CopyBuffer(const RenderBufferView& source, const RenderBufferView& target, UInt64 size, UInt64 sourceOffset, UInt64 targetOffset) { OpenGLBuffer& sourceBuffer = *static_cast(source.GetBuffer()); @@ -75,6 +84,14 @@ namespace Nz m_commandBuffer.CopyBuffer(allocation, targetBuffer.GetBuffer().GetObjectId(), size, sourceOffset, target.GetOffset() + targetOffset); } + void OpenGLCommandBufferBuilder::CopyTexture(const Texture& fromTexture, const Boxui& fromBox, TextureLayout fromLayout, const Texture& toTexture, const Vector3ui& toPos, TextureLayout toLayout) + { + const OpenGLTexture& sourceTexture = static_cast(fromTexture); + const OpenGLTexture& targetTexture = static_cast(toTexture); + + m_commandBuffer.CopyTexture(sourceTexture.GetTexture(), fromBox, targetTexture.GetTexture(), toPos); + } + void OpenGLCommandBufferBuilder::Draw(UInt32 vertexCount, UInt32 instanceCount, UInt32 firstVertex, UInt32 firstInstance) { m_commandBuffer.Draw(vertexCount, instanceCount, firstVertex, firstInstance); diff --git a/src/Nazara/OpenGLRenderer/Wrapper/Context.cpp b/src/Nazara/OpenGLRenderer/Wrapper/Context.cpp index f1d90c1c6..bff8883c2 100644 --- a/src/Nazara/OpenGLRenderer/Wrapper/Context.cpp +++ b/src/Nazara/OpenGLRenderer/Wrapper/Context.cpp @@ -249,7 +249,7 @@ namespace Nz::GL } } - bool Context::BlitTexture(const Texture& source, const Texture& destination, const Boxui& srcBox, const Vector3ui& dstPos, SamplerFilter filter) const + bool Context::BlitTexture(const Texture& source, const Texture& destination, const Boxui& srcBox, const Boxui& dstBox, SamplerFilter filter) const { if (!m_blitFramebuffers && !InitializeBlitFramebuffers()) return false; @@ -277,7 +277,7 @@ namespace Nz::GL return false; } - glBlitFramebuffer(srcBox.x, srcBox.y, srcBox.x + srcBox.width, srcBox.y + srcBox.height, dstPos.x, dstPos.y, dstPos.x + srcBox.width, dstPos.y + srcBox.height, GL_COLOR_BUFFER_BIT, ToOpenGL(filter)); + glBlitFramebuffer(srcBox.x, srcBox.y, srcBox.x + srcBox.width, srcBox.y + srcBox.height, dstBox.x, dstBox.y, dstBox.x + dstBox.width, dstBox.y + srcBox.height, GL_COLOR_BUFFER_BIT, ToOpenGL(filter)); return true; } @@ -296,7 +296,7 @@ namespace Nz::GL bool Context::CopyTexture(const Texture& source, const Texture& destination, const Boxui& srcBox, const Vector3ui& dstPos) const { // Use glCopyImageSubData if available - if (glCopyImageSubData && false) + if (glCopyImageSubData) { GLuint srcImage = source.GetObjectId(); GLenum srcTarget = ToOpenGL(source.GetTarget()); @@ -310,7 +310,7 @@ namespace Nz::GL else { // If glCopyImageSubData is not available, fallback to framebuffer blit - return BlitTexture(source, destination, srcBox, dstPos, SamplerFilter::Nearest); + return BlitTexture(source, destination, srcBox, Boxui(dstPos.x, dstPos.y, dstPos.z, srcBox.width, srcBox.height, srcBox.depth), SamplerFilter::Nearest); } } diff --git a/src/Nazara/VulkanRenderer/VulkanCommandBufferBuilder.cpp b/src/Nazara/VulkanRenderer/VulkanCommandBufferBuilder.cpp index bacc8e278..da1d6462c 100644 --- a/src/Nazara/VulkanRenderer/VulkanCommandBufferBuilder.cpp +++ b/src/Nazara/VulkanRenderer/VulkanCommandBufferBuilder.cpp @@ -110,6 +110,50 @@ namespace Nz m_commandBuffer.BindVertexBuffer(binding, vkBuffer.GetBuffer(), offset); } + void VulkanCommandBufferBuilder::BlitTexture(const Texture& fromTexture, const Boxui& fromBox, TextureLayout fromLayout, const Texture& toTexture, const Boxui& toBox, TextureLayout toLayout, SamplerFilter filter) + { + const VulkanTexture& vkFromTexture = static_cast(fromTexture); + const VulkanTexture& vkToTexture = static_cast(toTexture); + + VkImageSubresourceLayers todo = { + VK_IMAGE_ASPECT_COLOR_BIT, + 1, + 0, + 1 + }; + + VkImageBlit region = { + todo, + { + { + SafeCast(fromBox.x), + SafeCast(fromBox.y), + SafeCast(fromBox.z) + }, + { + SafeCast(fromBox.x + fromBox.width), + SafeCast(fromBox.y + fromBox.height), + SafeCast(fromBox.z + fromBox.depth) + } + }, + todo, + { + { + SafeCast(toBox.x), + SafeCast(toBox.y), + SafeCast(toBox.z) + }, + { + SafeCast(toBox.x + toBox.width), + SafeCast(toBox.y + toBox.height), + SafeCast(toBox.z + toBox.depth) + } + }, + }; + + m_commandBuffer.BlitImage(vkFromTexture.GetImage(), ToVulkan(fromLayout), vkToTexture.GetImage(), ToVulkan(toLayout), region, ToVulkan(filter)); + } + void VulkanCommandBufferBuilder::CopyBuffer(const RenderBufferView& source, const RenderBufferView& target, UInt64 size, UInt64 sourceOffset, UInt64 targetOffset) { VulkanBuffer& sourceBuffer = *static_cast(source.GetBuffer()); @@ -126,6 +170,40 @@ namespace Nz m_commandBuffer.CopyBuffer(vkAllocation.buffer, targetBuffer.GetBuffer(), size, vkAllocation.offset + sourceOffset, target.GetOffset() + targetOffset); } + void VulkanCommandBufferBuilder::CopyTexture(const Texture& fromTexture, const Boxui& fromBox, TextureLayout fromLayout, const Texture& toTexture, const Vector3ui& toPos, TextureLayout toLayout) + { + const VulkanTexture& vkFromTexture = static_cast(fromTexture); + const VulkanTexture& vkToTexture = static_cast(toTexture); + + VkImageSubresourceLayers todo = { + VK_IMAGE_ASPECT_COLOR_BIT, + 1, + 0, + 1 + }; + + VkImageCopy region = { + todo, + { + SafeCast(fromBox.x), + SafeCast(fromBox.y), + SafeCast(fromBox.z) + }, + { + SafeCast(toPos.x), + SafeCast(toPos.y), + SafeCast(toPos.z), + }, + { + SafeCast(fromBox.width), + SafeCast(fromBox.height), + SafeCast(fromBox.depth) + } + }; + + m_commandBuffer.CopyImage(vkFromTexture.GetImage(), ToVulkan(fromLayout), vkToTexture.GetImage(), ToVulkan(toLayout), region); + } + void VulkanCommandBufferBuilder::Draw(UInt32 vertexCount, UInt32 instanceCount, UInt32 firstVertex, UInt32 firstInstance) { m_commandBuffer.Draw(vertexCount, instanceCount, firstVertex, firstInstance);