From 62ccd8098817d514f657de4fbcec02008cba37c4 Mon Sep 17 00:00:00 2001 From: mogemimi Date: Sun, 29 Sep 2019 00:44:47 +0900 Subject: [PATCH] Add support for getting texture pixel data from render target --- include/Pomdog/Graphics/RenderTarget2D.hpp | 12 +++ src/Graphics/RenderTarget2D.cpp | 17 ++++ .../RenderTarget2DDirect3D11.cpp | 93 +++++++++++++++++-- .../RenderTarget2DDirect3D11.hpp | 9 ++ src/RenderSystem.GL4/RenderTarget2DGL4.cpp | 19 ++++ src/RenderSystem.GL4/RenderTarget2DGL4.hpp | 9 ++ src/RenderSystem.GL4/Texture2DGL4.cpp | 34 +++++++ src/RenderSystem.GL4/Texture2DGL4.hpp | 9 ++ .../RenderTarget2DMetal.hpp | 9 ++ src/RenderSystem.Metal/RenderTarget2DMetal.mm | 24 +++++ src/RenderSystem/NativeRenderTarget2D.hpp | 13 +++ 11 files changed, 240 insertions(+), 8 deletions(-) diff --git a/include/Pomdog/Graphics/RenderTarget2D.hpp b/include/Pomdog/Graphics/RenderTarget2D.hpp index b94657995..45d824c99 100644 --- a/include/Pomdog/Graphics/RenderTarget2D.hpp +++ b/include/Pomdog/Graphics/RenderTarget2D.hpp @@ -68,6 +68,18 @@ class POMDOG_EXPORT RenderTarget2D final : public Texture { /// Gets the size of the texture resource. Rectangle GetBounds() const; + /// Copies the pixel data from texture to memory. + template + void GetData(T* result, std::size_t startIndex, std::size_t elementCount) const + { + static_assert(std::is_pod_v, "You can only use plain-old-data types."); + static_assert(!std::is_void_v); + this->GetData(static_cast(result), sizeof(T) * startIndex, sizeof(T) * elementCount); + } + + /// Copies the pixel data from texture to memory. + void GetData(void* result, std::size_t offsetInBytes, std::size_t sizeInBytes) const; + /// Gets the pointer of the native render target. Detail::NativeRenderTarget2D* GetNativeRenderTarget2D(); diff --git a/src/Graphics/RenderTarget2D.cpp b/src/Graphics/RenderTarget2D.cpp index dbe3d545d..7decef8da 100644 --- a/src/Graphics/RenderTarget2D.cpp +++ b/src/Graphics/RenderTarget2D.cpp @@ -116,6 +116,23 @@ Rectangle RenderTarget2D::GetBounds() const return Rectangle{0, 0, pixelWidth, pixelHeight}; } +void RenderTarget2D::GetData(void* result, std::size_t offsetInBytes, std::size_t sizeInBytes) const +{ + POMDOG_ASSERT(offsetInBytes >= 0); + POMDOG_ASSERT(sizeInBytes > 0); + POMDOG_ASSERT(result != nullptr); + POMDOG_ASSERT(nativeRenderTarget2D); + + nativeRenderTarget2D->GetData( + result, + offsetInBytes, + sizeInBytes, + pixelWidth, + pixelHeight, + levelCount, + format); +} + Detail::NativeRenderTarget2D* RenderTarget2D::GetNativeRenderTarget2D() { return nativeRenderTarget2D.get(); diff --git a/src/RenderSystem.Direct3D11/RenderTarget2DDirect3D11.cpp b/src/RenderSystem.Direct3D11/RenderTarget2DDirect3D11.cpp index 0a247e1fd..c08c24583 100644 --- a/src/RenderSystem.Direct3D11/RenderTarget2DDirect3D11.cpp +++ b/src/RenderSystem.Direct3D11/RenderTarget2DDirect3D11.cpp @@ -18,7 +18,6 @@ void BuildRenderTarget( std::int32_t pixelWidth, std::int32_t pixelHeight, std::int32_t levelCount, - bool isSharedTexture, ComPtr & texture2D, ComPtr & renderTargetView, ComPtr & textureResourceView) @@ -40,10 +39,7 @@ void BuildRenderTarget( textureDesc.Usage = D3D11_USAGE_DEFAULT; textureDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; textureDesc.CPUAccessFlags = 0; - textureDesc.MiscFlags = (isSharedTexture ? D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX : 0); - - POMDOG_ASSERT(isSharedTexture - ? textureDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM : true); + textureDesc.MiscFlags = 0; HRESULT hr = device->CreateTexture2D(&textureDesc, nullptr, &texture2D); @@ -182,7 +178,7 @@ RenderTarget2DDirect3D11::RenderTarget2DDirect3D11( std::int32_t levelCount, SurfaceFormat format, DepthFormat depthStencilFormat, - std::int32_t multiSampleCount) + [[maybe_unused]] std::int32_t multiSampleCount) { POMDOG_ASSERT(levelCount > 0); @@ -190,7 +186,7 @@ RenderTarget2DDirect3D11::RenderTarget2DDirect3D11( UNREFERENCED_PARAMETER(multiSampleCount); BuildRenderTarget(device, format, pixelWidth, pixelHeight, levelCount, - false, texture2D, renderTargetView, textureResourceView); + texture2D, renderTargetView, textureResourceView); BuildDepthBuffer(device, depthStencilFormat, pixelWidth, pixelHeight, levelCount, depthStencil, depthStencilView); @@ -202,7 +198,7 @@ RenderTarget2DDirect3D11::RenderTarget2DDirect3D11( std::int32_t pixelWidth, std::int32_t pixelHeight, DepthFormat depthStencilFormat, - std::int32_t multiSampleCount) + [[maybe_unused]] std::int32_t multiSampleCount) { ///@todo MSAA is not implemented yet UNREFERENCED_PARAMETER(multiSampleCount); @@ -216,6 +212,87 @@ RenderTarget2DDirect3D11::RenderTarget2DDirect3D11( backBufferMipLevels, depthStencil, depthStencilView); } +void RenderTarget2DDirect3D11::GetData( + void* result, + std::size_t offsetInBytes, + std::size_t sizeInBytes, + [[maybe_unused]] std::int32_t pixelWidth, + [[maybe_unused]] std::int32_t pixelHeight, + [[maybe_unused]] std::int32_t levelCount, + [[maybe_unused]] SurfaceFormat format) const +{ + POMDOG_ASSERT(texture2D); + + // NOTE: Get the device context + ComPtr device; + texture2D->GetDevice(&device); + ComPtr deviceContext; + device->GetImmediateContext(&deviceContext); + + POMDOG_ASSERT(deviceContext != nullptr); + + // NOTE: Map the texture + D3D11_MAPPED_SUBRESOURCE mappedResource; + auto hr = deviceContext->Map( + texture2D.Get(), + 0, + D3D11_MAP_READ, + 0, + &mappedResource); + + ComPtr mappedTexture; + + if (!FAILED(hr)) { + mappedTexture = texture2D; + } + else if (hr == E_INVALIDARG) { + // NOTE: If we failed to map the texture, copy it to a staging resource. + D3D11_TEXTURE2D_DESC desc; + texture2D->GetDesc(&desc); + + desc.Usage = D3D11_USAGE_STAGING; + desc.BindFlags = 0; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.MiscFlags = 0; + + ComPtr stagingTexture; + hr = device->CreateTexture2D(&desc, nullptr, &stagingTexture); + if (FAILED(hr)) { + // FUS RO DAH! + POMDOG_THROW_EXCEPTION(std::runtime_error, "Failed to create staging texture"); + } + + // NOTE: Copy the texture to a staging resource. + deviceContext->CopyResource(stagingTexture.Get(), texture2D.Get()); + + // NOTE: Map the staging resource + hr = deviceContext->Map( + stagingTexture.Get(), + 0, + D3D11_MAP_READ, + 0, + &mappedResource); + + if (FAILED(hr)) { + // FUS RO DAH! + POMDOG_THROW_EXCEPTION(std::runtime_error, "Failed to map staging texture"); + } + + mappedTexture = std::move(stagingTexture); + } + else { + // FUS RO DAH! + POMDOG_THROW_EXCEPTION(std::runtime_error, "Failed to map buffer"); + } + + POMDOG_ASSERT(result != nullptr); + POMDOG_ASSERT(sizeInBytes > 0); + std::memcpy(result, reinterpret_cast(mappedResource.pData) + offsetInBytes, sizeInBytes); + + POMDOG_ASSERT(mappedTexture != nullptr); + deviceContext->Unmap(mappedTexture.Get(), 0); +} + ID3D11RenderTargetView* RenderTarget2DDirect3D11::GetRenderTargetView() const { POMDOG_ASSERT(renderTargetView); diff --git a/src/RenderSystem.Direct3D11/RenderTarget2DDirect3D11.hpp b/src/RenderSystem.Direct3D11/RenderTarget2DDirect3D11.hpp index 554482036..757dcc6e7 100644 --- a/src/RenderSystem.Direct3D11/RenderTarget2DDirect3D11.hpp +++ b/src/RenderSystem.Direct3D11/RenderTarget2DDirect3D11.hpp @@ -28,6 +28,15 @@ class RenderTarget2DDirect3D11 final : public NativeRenderTarget2D { DepthFormat depthStencilFormat, std::int32_t multiSampleCount); + void GetData( + void* result, + std::size_t offsetInBytes, + std::size_t sizeInBytes, + std::int32_t pixelWidth, + std::int32_t pixelHeight, + std::int32_t levelCount, + SurfaceFormat format) const override; + ID3D11RenderTargetView* GetRenderTargetView() const; ID3D11DepthStencilView* GetDepthStencilView() const; diff --git a/src/RenderSystem.GL4/RenderTarget2DGL4.cpp b/src/RenderSystem.GL4/RenderTarget2DGL4.cpp index 1b9a9b395..07f0b5c55 100644 --- a/src/RenderSystem.GL4/RenderTarget2DGL4.cpp +++ b/src/RenderSystem.GL4/RenderTarget2DGL4.cpp @@ -68,6 +68,25 @@ RenderTarget2DGL4::~RenderTarget2DGL4() } } +void RenderTarget2DGL4::GetData( + void* result, + std::size_t offsetInBytes, + std::size_t sizeInBytes, + std::int32_t pixelWidth, + std::int32_t pixelHeight, + std::int32_t levelCount, + SurfaceFormat format) const +{ + texture.GetData( + result, + offsetInBytes, + sizeInBytes, + pixelWidth, + pixelHeight, + levelCount, + format); +} + void RenderTarget2DGL4::BindToFramebuffer(GLenum attachmentPoint) { GLenum textureTarget = (multiSampleEnabled diff --git a/src/RenderSystem.GL4/RenderTarget2DGL4.hpp b/src/RenderSystem.GL4/RenderTarget2DGL4.hpp index 26dabd94c..139c74c22 100644 --- a/src/RenderSystem.GL4/RenderTarget2DGL4.hpp +++ b/src/RenderSystem.GL4/RenderTarget2DGL4.hpp @@ -25,6 +25,15 @@ class RenderTarget2DGL4 final : public NativeRenderTarget2D { ~RenderTarget2DGL4(); + void GetData( + void* result, + std::size_t offsetInBytes, + std::size_t sizeInBytes, + std::int32_t pixelWidth, + std::int32_t pixelHeight, + std::int32_t levelCount, + SurfaceFormat format) const override; + void BindToFramebuffer(GLenum attachmentPoint); void UnbindFromFramebuffer(GLenum attachmentPoint); diff --git a/src/RenderSystem.GL4/Texture2DGL4.cpp b/src/RenderSystem.GL4/Texture2DGL4.cpp index 0e53257f5..fb9aab24c 100644 --- a/src/RenderSystem.GL4/Texture2DGL4.cpp +++ b/src/RenderSystem.GL4/Texture2DGL4.cpp @@ -255,6 +255,40 @@ Texture2DGL4::~Texture2DGL4() } } +void Texture2DGL4::GetData( + void* result, + [[maybe_unused]] std::size_t offsetInBytes, + std::size_t sizeInBytes, + std::int32_t pixelWidth, + std::int32_t pixelHeight, + [[maybe_unused]] std::int32_t levelCount, + SurfaceFormat format) const +{ + const auto oldTexture = TypesafeHelperGL4::Get(); + ScopeGuard scope([&] { TypesafeHelperGL4::BindTexture(oldTexture); }); + + POMDOG_ASSERT(textureObject); + TypesafeHelperGL4::BindTexture(*textureObject); + POMDOG_CHECK_ERROR_GL4("glBindTexture"); + + auto const formatComponents = ToFormatComponents(format); + auto const pixelFundamentalType = ToPixelFundamentalType(format); + auto const bytesPerBlock = SurfaceFormatHelper::ToBytesPerBlock(format); + + // FIXME: Not implemented yet. + POMDOG_ASSERT(sizeInBytes >= static_cast(bytesPerBlock * pixelWidth * pixelHeight)); + if (sizeInBytes < static_cast(bytesPerBlock * pixelWidth * pixelHeight)) { + return; + } + + glGetTexImage( + GL_TEXTURE_2D, + 0, + formatComponents, + pixelFundamentalType, + result); +} + void Texture2DGL4::SetData(std::int32_t pixelWidth, std::int32_t pixelHeight, std::int32_t levelCount, SurfaceFormat format, const void* pixelData) { diff --git a/src/RenderSystem.GL4/Texture2DGL4.hpp b/src/RenderSystem.GL4/Texture2DGL4.hpp index ee399442f..040828bc9 100644 --- a/src/RenderSystem.GL4/Texture2DGL4.hpp +++ b/src/RenderSystem.GL4/Texture2DGL4.hpp @@ -22,6 +22,15 @@ class Texture2DGL4 final : public NativeTexture2D { ~Texture2DGL4() override; + void GetData( + void* result, + std::size_t offsetInBytes, + std::size_t sizeInBytes, + std::int32_t pixelWidth, + std::int32_t pixelHeight, + std::int32_t levelCount, + SurfaceFormat format) const; + void SetData( std::int32_t pixelWidth, std::int32_t pixelHeight, diff --git a/src/RenderSystem.Metal/RenderTarget2DMetal.hpp b/src/RenderSystem.Metal/RenderTarget2DMetal.hpp index f4d14aab1..d9d70fcd9 100644 --- a/src/RenderSystem.Metal/RenderTarget2DMetal.hpp +++ b/src/RenderSystem.Metal/RenderTarget2DMetal.hpp @@ -19,6 +19,15 @@ class RenderTarget2DMetal final : public NativeRenderTarget2D { DepthFormat depthStencilFormat, std::int32_t multiSampleCount); + void GetData( + void* result, + std::size_t offsetInBytes, + std::size_t sizeInBytes, + std::int32_t pixelWidth, + std::int32_t pixelHeight, + std::int32_t levelCount, + SurfaceFormat format) const override; + id GetTexture() const noexcept; id GetDepthStencilTexture() const noexcept; diff --git a/src/RenderSystem.Metal/RenderTarget2DMetal.mm b/src/RenderSystem.Metal/RenderTarget2DMetal.mm index bbf2bfb49..7158438f7 100644 --- a/src/RenderSystem.Metal/RenderTarget2DMetal.mm +++ b/src/RenderSystem.Metal/RenderTarget2DMetal.mm @@ -2,6 +2,7 @@ #include "RenderTarget2DMetal.hpp" #include "MetalFormatHelper.hpp" +#include "../RenderSystem/SurfaceFormatHelper.hpp" #include "Pomdog/Graphics/DepthFormat.hpp" #include "Pomdog/Logging/Log.hpp" #include "Pomdog/Utility/Assert.hpp" @@ -74,6 +75,29 @@ } } +void RenderTarget2DMetal::GetData( + void* result, + std::size_t offsetInBytes, + std::size_t sizeInBytes, + std::int32_t pixelWidth, + std::int32_t pixelHeight, + [[maybe_unused]] std::int32_t levelCount, + SurfaceFormat format) const +{ + POMDOG_ASSERT(texture != nil); + POMDOG_ASSERT(result != nullptr); + + auto const bytesPerPixel = SurfaceFormatHelper::ToBytesPerBlock(format); + + // FIXME: Not implemented yet. + POMDOG_ASSERT(offsetInBytes == 0); + POMDOG_ASSERT(sizeInBytes == static_cast(bytesPerPixel * pixelWidth * pixelHeight)); + MTLRegion region = MTLRegionMake2D(0, 0, pixelWidth, pixelHeight); + + // NOTE: Don't use getBytes() for textures with MTLResourceStorageModePrivate. + [texture getBytes:result bytesPerRow:(bytesPerPixel * pixelWidth) fromRegion:region mipmapLevel:0]; +} + id RenderTarget2DMetal::GetTexture() const noexcept { return texture; diff --git a/src/RenderSystem/NativeRenderTarget2D.hpp b/src/RenderSystem/NativeRenderTarget2D.hpp index 7e6040a67..b3be2b80a 100644 --- a/src/RenderSystem/NativeRenderTarget2D.hpp +++ b/src/RenderSystem/NativeRenderTarget2D.hpp @@ -2,6 +2,10 @@ #pragma once +#include "Pomdog/Graphics/detail/ForwardDeclarations.hpp" +#include +#include + namespace Pomdog::Detail { class NativeRenderTarget2D { @@ -11,6 +15,15 @@ class NativeRenderTarget2D { NativeRenderTarget2D & operator=(const NativeRenderTarget2D&) = delete; virtual ~NativeRenderTarget2D() = default; + + virtual void GetData( + void* result, + std::size_t offsetInBytes, + std::size_t sizeInBytes, + std::int32_t pixelWidth, + std::int32_t pixelHeight, + std::int32_t levelCount, + SurfaceFormat format) const = 0; }; } // namespace Pomdog::Detail