Initial Texture Manager Implementation + Maxwell3D Render Target

Implement the groundwork for the texture manager to be able to report basic overlaps and be extended to support more in the future. The Maxwell3D registers `RenderTargetControl`, `RenderTarget` and a stub for `ClearBuffers` were implemented. 

A lot of changes were also made to `GuestTexture`/`Texture` for supporting mipmapping and multiple array layers alongside significant architectural changes to `GuestTexture` effectively disconnecting it from `Texture` with it no longer being a parent rather an object that can be used to create a `Texture` object.

Note: Support for fragmented CPU mappings hasn't been added for texture synchronization yet
This commit is contained in:
PixelyIon
2021-08-21 01:47:13 +05:30
parent 8cba1edf6d
commit 270f2db1d2
17 changed files with 757 additions and 298 deletions

View File

@ -4,6 +4,8 @@
#pragma once
#include <vulkan/vulkan_raii.hpp>
#include <gpu.h>
#include <gpu/texture/format.h>
#include <soc/gm20b/engines/maxwell/types.h>
namespace skyline::gpu::context {
@ -11,26 +13,141 @@ namespace skyline::gpu::context {
/**
* @brief Host-equivalent context for state of the Maxwell3D engine on the guest
* @note This class is **NOT** thread-safe and should not be utilized by multiple threads concurrently
*/
class GraphicsContext {
private:
GPU &gpu;
std::array<vk::Viewport, maxwell3d::ViewportCount> viewports;
struct RenderTarget {
bool disabled{}; //!< If this RT has been disabled and will be an unbound attachment instead
union {
u64 gpuAddress;
struct {
u32 gpuAddressHigh;
u32 gpuAddressLow;
};
};
GuestTexture guest;
std::optional<TextureView> view;
};
std::array<vk::Rect2D, maxwell3d::ViewportCount> scissors;
std::array<RenderTarget, maxwell3d::RenderTargetCount> renderTargets{}; //!< The target textures to render into as color attachments
maxwell3d::RenderTargetControl renderTargetControl{};
std::array<vk::Viewport, maxwell3d::ViewportCount> viewports;
vk::ClearColorValue clearColorValue{}; //!< The value written to a color buffer being cleared
std::array<vk::Rect2D, maxwell3d::ViewportCount> scissors; //!< The scissors applied to viewports/render targets for masking writes during draws or clears
constexpr static vk::Rect2D DefaultScissor{
.extent = {
.height = std::numeric_limits<i32>::max(),
.width = std::numeric_limits<i32>::max(),
}
.extent.height = std::numeric_limits<i32>::max(),
.extent.width = std::numeric_limits<i32>::max(),
}; //!< A scissor which displays the entire viewport, utilized when the viewport scissor is disabled
public:
GraphicsContext(GPU &gpu) : gpu(gpu) {
scissors.fill(DefaultScissor);
}
/* Render Targets + Render Target Control */
void SetRenderTargetAddressHigh(size_t index, u32 high) {
auto &renderTarget{renderTargets.at(index)};
renderTarget.gpuAddressHigh = high;
renderTarget.guest.mappings.clear();
renderTarget.view.reset();
}
void SetRenderTargetAddressLow(size_t index, u32 low) {
auto &renderTarget{renderTargets.at(index)};
renderTarget.gpuAddressLow = low;
renderTarget.guest.mappings.clear();
renderTarget.view.reset();
}
void SetRenderTargetAddressWidth(size_t index, u32 value) {
auto &renderTarget{renderTargets.at(index)};
renderTarget.guest.dimensions.width = value;
renderTarget.view.reset();
}
void SetRenderTargetAddressHeight(size_t index, u32 value) {
auto &renderTarget{renderTargets.at(index)};
renderTarget.guest.dimensions.height = value;
renderTarget.view.reset();
}
void SetRenderTargetAddressFormat(size_t index, maxwell3d::RenderTarget::ColorFormat format) {
auto &renderTarget{renderTargets.at(index)};
renderTarget.guest.format = [&]() -> texture::Format {
switch (format) {
case maxwell3d::RenderTarget::ColorFormat::None:
return {};
case maxwell3d::RenderTarget::ColorFormat::R8G8B8A8Unorm:
return format::RGBA8888Unorm;
default:
throw exception("Cannot translate the supplied RT format: 0x{:X}", static_cast<u32>(format));
}
}();
renderTarget.disabled = !renderTarget.guest.format;
renderTarget.view.reset();
}
void SetRenderTargetTileMode(size_t index, maxwell3d::RenderTarget::TileMode mode) {
auto &renderTarget{renderTargets.at(index)};
auto &config{renderTarget.guest.tileConfig};
if (mode.isLinear) {
config.mode = texture::TileMode::Linear;
} else [[likely]] {
config = texture::TileConfig{
.mode = texture::TileMode::Block,
.blockHeight = static_cast<u8>(1U << mode.blockHeightLog2),
.blockDepth = static_cast<u8>(1U << mode.blockDepthLog2),
};
}
renderTarget.view.reset();
}
void SetRenderTargetArrayMode(size_t index, maxwell3d::RenderTarget::ArrayMode mode) {
auto &renderTarget{renderTargets.at(index)};
renderTarget.guest.layerCount = mode.layerCount;
if (mode.volume)
throw exception("RT Array Volumes are not supported (with layer count = {})", mode.layerCount);
renderTarget.view.reset();
}
void SetRenderTargetLayerStride(size_t index, u32 layerStrideLsr2) {
auto &renderTarget{renderTargets.at(index)};
renderTarget.guest.layerStride = layerStrideLsr2 << 2;
renderTarget.view.reset();
}
void SetRenderTargetBaseLayer(size_t index, u32 baseArrayLayer) {
auto &renderTarget{renderTargets.at(index)};
renderTarget.guest.baseArrayLayer = baseArrayLayer;
if (baseArrayLayer > std::numeric_limits<u16>::max())
throw exception("Base array layer ({}) exceeds the range of array count ({}) (with layer count = {})", baseArrayLayer, std::numeric_limits<u16>::max(), renderTarget.guest.layerCount);
renderTarget.view.reset();
}
const TextureView *GetRenderTarget(size_t index) {
auto &renderTarget{renderTargets.at(index)};
if (renderTarget.disabled)
return nullptr;
else if (renderTarget.view)
return &*renderTarget.view;
if (renderTarget.guest.mappings.empty()) {
// TODO: Fill in mappings
return nullptr;
}
return &*(renderTarget.view = gpu.texture.FindOrCreate(renderTarget.guest));
}
void UpdateRenderTargetControl(maxwell3d::RenderTargetControl control) {
renderTargetControl = control;
}
/* Viewport Transforms */
/**
@ -55,6 +172,20 @@ namespace skyline::gpu::context {
viewport.maxDepth = scale + translate; // Counteract the subtraction of the maxDepth (p_z - o_z) by minDepth (o_z) for the host scale
}
/* Buffer Clears */
void UpdateClearColorValue(size_t index, u32 value) {
clearColorValue.uint32.at(index) = value;
}
void ClearBuffers(maxwell3d::ClearBuffers clear) {
auto renderTarget{GetRenderTarget(renderTargetControl.Map(clear.renderTargetId))};
if (renderTarget) {
std::lock_guard lock(*renderTarget->backing);
// TODO: Clear the buffer
}
}
/* Viewport Scissors */
void SetScissor(size_t index, std::optional<maxwell3d::Scissor> scissor) {

View File

@ -121,6 +121,6 @@ namespace skyline::gpu::memory {
VmaAllocationInfo allocationInfo;
ThrowOnFail(vmaCreateImage(vmaAllocator, &static_cast<const VkImageCreateInfo &>(createInfo), &allocationCreateInfo, &image, &allocation, &allocationInfo));
return Image(reinterpret_cast<u8 *>(allocationInfo.pMappedData), vmaAllocator, image, allocation);
return Image(vmaAllocator, image, allocation);
}
}

View File

@ -114,8 +114,8 @@ namespace skyline::gpu {
if (swapchainFormat != format) {
auto formats{gpu.vkPhysicalDevice.getSurfaceFormatsKHR(**vkSurface)};
if (std::find(formats.begin(), formats.end(), vk::SurfaceFormatKHR{format, vk::ColorSpaceKHR::eSrgbNonlinear}) == formats.end())
throw exception("Surface doesn't support requested image format '{}' with colorspace '{}'", vk::to_string(format), vk::to_string(vk::ColorSpaceKHR::eSrgbNonlinear));
if (std::find(formats.begin(), formats.end(), vk::SurfaceFormatKHR{*format, vk::ColorSpaceKHR::eSrgbNonlinear}) == formats.end())
throw exception("Surface doesn't support requested image format '{}' with colorspace '{}'", vk::to_string(*format), vk::to_string(vk::ColorSpaceKHR::eSrgbNonlinear));
}
constexpr vk::ImageUsageFlags presentUsage{vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst};
@ -125,7 +125,7 @@ namespace skyline::gpu {
vkSwapchain.emplace(gpu.vkDevice, vk::SwapchainCreateInfoKHR{
.surface = **vkSurface,
.minImageCount = minImageCount,
.imageFormat = format,
.imageFormat = *format,
.imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear,
.imageExtent = extent,
.imageArrayLayers = 1,
@ -142,7 +142,7 @@ namespace skyline::gpu {
for (size_t index{}; index < vkImages.size(); index++) {
auto &slot{images[index]};
slot = std::make_shared<Texture>(*state.gpu, vkImages[index], extent, format::GetFormat(format), vk::ImageLayout::eUndefined, vk::ImageTiling::eOptimal);
slot = std::make_shared<Texture>(*state.gpu, vkImages[index], extent, format, vk::ImageLayout::eUndefined, vk::ImageTiling::eOptimal);
slot->TransitionLayout(vk::ImageLayout::ePresentSrcKHR);
}
for (size_t index{vkImages.size()}; index < MaxSwapchainImageCount; index++)
@ -235,7 +235,11 @@ namespace skyline::gpu {
}
std::ignore = gpu.vkDevice.waitForFences(*acquireFence, true, std::numeric_limits<u64>::max());
images.at(nextImage.second)->CopyFrom(texture);
images.at(nextImage.second)->CopyFrom(texture, vk::ImageSubresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = 1,
.layerCount = 1,
});
if (timestamp) {
// If the timestamp is specified, we need to convert it from the util::GetTimeNs base to the CLOCK_MONOTONIC one

View File

@ -6,15 +6,17 @@
#include "texture.h"
namespace skyline::gpu::format {
using Format = gpu::texture::Format;
using Format = gpu::texture::FormatBase;
using vkf = vk::Format;
using vka = vk::ImageAspectFlagBits;
constexpr Format RGBA8888Unorm{sizeof(u8) * 4, 1, 1, vk::Format::eR8G8B8A8Unorm}; //!< 8-bits per channel 4-channel pixels
constexpr Format RGB565Unorm{sizeof(u8) * 2, 1, 1, vk::Format::eR5G6B5UnormPack16}; //!< Red channel: 5-bit, Green channel: 6-bit, Blue channel: 5-bit
constexpr Format RGBA8888Unorm{sizeof(u8) * 4, 1, 1, vkf::eR8G8B8A8Unorm, vka::eColor}; //!< 8-bits per channel 4-channel pixels
constexpr Format RGB565Unorm{sizeof(u8) * 2, 1, 1, vkf::eR5G6B5UnormPack16, vka::eColor}; //!< Red channel: 5-bit, Green channel: 6-bit, Blue channel: 5-bit
/**
* @brief Converts a Vulkan format to a Skyline format
*/
constexpr const Format &GetFormat(vk::Format format) {
constexpr gpu::texture::Format GetFormat(vk::Format format) {
switch (format) {
case vk::Format::eR8G8B8A8Unorm:
return RGBA8888Unorm;

View File

@ -7,66 +7,48 @@
#include "texture.h"
namespace skyline::gpu {
GuestTexture::GuestTexture(const DeviceState &state, u8 *pointer, texture::Dimensions dimensions, const texture::Format &format, texture::TileMode tiling, texture::TileConfig layout) : state(state), pointer(pointer), dimensions(dimensions), format(format), tileMode(tiling), tileConfig(layout) {}
std::shared_ptr<Texture> GuestTexture::InitializeTexture(vk::Image backing, texture::Dimensions pDimensions, const texture::Format &pFormat, std::optional<vk::ImageTiling> tiling, vk::ImageLayout layout, texture::Swizzle swizzle) {
if (!host.expired())
throw exception("Trying to create multiple Texture objects from a single GuestTexture");
auto sharedHost{std::make_shared<Texture>(*state.gpu, backing, shared_from_this(), pDimensions ? pDimensions : dimensions, pFormat ? pFormat : format, layout, tiling ? *tiling : (tileMode == texture::TileMode::Block) ? vk::ImageTiling::eOptimal : vk::ImageTiling::eLinear, swizzle)};
host = sharedHost;
return sharedHost;
}
std::shared_ptr<Texture> GuestTexture::InitializeTexture(vk::raii::Image &&backing, std::optional<vk::ImageTiling> tiling, vk::ImageLayout layout, const texture::Format &pFormat, texture::Dimensions pDimensions, texture::Swizzle swizzle) {
if (!host.expired())
throw exception("Trying to create multiple Texture objects from a single GuestTexture");
auto sharedHost{std::make_shared<Texture>(*state.gpu, std::move(backing), shared_from_this(), pDimensions ? pDimensions : dimensions, pFormat ? pFormat : format, layout, tiling ? *tiling : (tileMode == texture::TileMode::Block) ? vk::ImageTiling::eOptimal : vk::ImageTiling::eLinear, swizzle)};
host = sharedHost;
return sharedHost;
}
std::shared_ptr<Texture> GuestTexture::CreateTexture(vk::ImageUsageFlags usage, std::optional<vk::ImageTiling> pTiling, vk::ImageLayout initialLayout, const texture::Format &pFormat, texture::Dimensions pDimensions, texture::Swizzle swizzle) {
if (!host.expired())
throw exception("Trying to create multiple Texture objects from a single GuestTexture");
pDimensions = pDimensions ? pDimensions : dimensions;
const auto &lFormat{pFormat ? pFormat : format};
auto tiling{pTiling ? *pTiling : (tileMode == texture::TileMode::Block) ? vk::ImageTiling::eOptimal : vk::ImageTiling::eLinear};
vk::ImageCreateInfo imageCreateInfo{
.imageType = pDimensions.GetType(),
.format = lFormat,
.extent = pDimensions,
.mipLevels = 1,
.arrayLayers = 1,
.samples = vk::SampleCountFlagBits::e1,
.tiling = tiling,
.usage = usage | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst,
.sharingMode = vk::SharingMode::eExclusive,
.queueFamilyIndexCount = 1,
.pQueueFamilyIndices = &state.gpu->vkQueueFamilyIndex,
.initialLayout = initialLayout,
};
auto sharedHost{std::make_shared<Texture>(*state.gpu, tiling != vk::ImageTiling::eLinear ? state.gpu->memory.AllocateImage(imageCreateInfo) : state.gpu->memory.AllocateMappedImage(imageCreateInfo), shared_from_this(), pDimensions, lFormat, initialLayout, tiling, swizzle)};
host = sharedHost;
return sharedHost;
}
Texture::Texture(GPU &gpu, BackingType &&backing, std::shared_ptr<GuestTexture> guest, texture::Dimensions dimensions, const texture::Format &format, vk::ImageLayout layout, vk::ImageTiling tiling, vk::ComponentMapping mapping) : gpu(gpu), backing(std::move(backing)), layout(layout), guest(std::move(guest)), dimensions(dimensions), format(format), tiling(tiling), mapping(mapping) {
Texture::Texture(GPU &gpu, BackingType &&backing, GuestTexture guest, texture::Dimensions dimensions, texture::Format format, vk::ImageLayout layout, vk::ImageTiling tiling, u32 mipLevels, u32 layerCount, vk::SampleCountFlagBits sampleCount) : gpu(gpu), backing(std::move(backing)), layout(layout), guest(std::move(guest)), dimensions(dimensions), format(format), tiling(tiling), mipLevels(mipLevels), layerCount(layerCount), sampleCount(sampleCount) {
if (GetBacking())
SynchronizeHost();
}
Texture::Texture(GPU &gpu, BackingType &&backing, texture::Dimensions dimensions, const texture::Format &format, vk::ImageLayout layout, vk::ImageTiling tiling, vk::ComponentMapping mapping) : gpu(gpu), backing(std::move(backing)), guest(nullptr), dimensions(dimensions), format(format), layout(layout), tiling(tiling), mapping(mapping) {}
Texture::Texture(GPU &gpu, BackingType &&backing, texture::Dimensions dimensions, texture::Format format, vk::ImageLayout layout, vk::ImageTiling tiling, u32 mipLevels, u32 layerCount, vk::SampleCountFlagBits sampleCount) : gpu(gpu), backing(std::move(backing)), dimensions(dimensions), format(format), layout(layout), tiling(tiling), mipLevels(mipLevels), layerCount(layerCount), sampleCount(sampleCount) {}
Texture::Texture(GPU &gpu, texture::Dimensions dimensions, const texture::Format &format, vk::ImageLayout initialLayout, vk::ImageUsageFlags usage, vk::ImageTiling tiling, vk::ComponentMapping mapping) : gpu(gpu), guest(nullptr), dimensions(dimensions), format(format), layout(initialLayout), tiling(tiling), mapping(mapping) {
Texture::Texture(GPU &pGpu, GuestTexture pGuest)
: gpu(pGpu),
guest(std::move(pGuest)),
dimensions(guest->dimensions),
format(guest->format),
layout(vk::ImageLayout::eGeneral),
tiling((guest->tileConfig.mode == texture::TileMode::Block) ? vk::ImageTiling::eOptimal : vk::ImageTiling::eLinear),
mipLevels(1),
layerCount(guest->layerCount),
sampleCount(vk::SampleCountFlagBits::e1) {
vk::ImageCreateInfo imageCreateInfo{
.imageType = guest->dimensions.GetType(),
.format = *guest->format,
.extent = guest->dimensions,
.mipLevels = 1,
.arrayLayers = guest->layerCount,
.samples = vk::SampleCountFlagBits::e1,
.tiling = tiling,
.usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst,
.sharingMode = vk::SharingMode::eExclusive,
.queueFamilyIndexCount = 1,
.pQueueFamilyIndices = &gpu.vkQueueFamilyIndex,
.initialLayout = layout,
};
backing = tiling != vk::ImageTiling::eLinear ? gpu.memory.AllocateImage(imageCreateInfo) : gpu.memory.AllocateMappedImage(imageCreateInfo);
}
Texture::Texture(GPU &gpu, texture::Dimensions dimensions, texture::Format format, vk::ImageLayout initialLayout, vk::ImageUsageFlags usage, vk::ImageTiling tiling, u32 mipLevels, u32 layerCount, vk::SampleCountFlagBits sampleCount) : gpu(gpu), dimensions(dimensions), format(format), layout(initialLayout), tiling(tiling), mipLevels(mipLevels), layerCount(layerCount), sampleCount(sampleCount) {
vk::ImageCreateInfo imageCreateInfo{
.imageType = dimensions.GetType(),
.format = format,
.format = *format,
.extent = dimensions,
.mipLevels = 1,
.arrayLayers = 1,
.samples = vk::SampleCountFlagBits::e1,
.mipLevels = mipLevels,
.arrayLayers = layerCount,
.samples = sampleCount,
.tiling = tiling,
.usage = usage | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst,
.sharingMode = vk::SharingMode::eExclusive,
@ -132,10 +114,14 @@ namespace skyline::gpu {
void Texture::SynchronizeHost() {
if (!guest)
throw exception("Synchronization of host textures requires a valid guest texture to synchronize from");
else if (guest->mappings.size() != 1)
throw exception("Synchronization of non-contigious textures is not supported");
else if (guest->dimensions != dimensions)
throw exception("Guest and host dimensions being different is not supported currently");
TRACE_EVENT("gpu", "Texture::SynchronizeHost");
auto pointer{guest->pointer};
auto size{format.GetSize(dimensions)};
auto pointer{guest->mappings[0].data()};
auto size{format->GetSize(dimensions)};
u8 *bufferData;
auto stagingBuffer{[&]() -> std::shared_ptr<memory::StagingBuffer> {
@ -154,24 +140,24 @@ namespace skyline::gpu {
}
}()};
if (guest->tileMode == texture::TileMode::Block) {
if (guest->tileConfig.mode == texture::TileMode::Block) {
// Reference on Block-linear tiling: https://gist.github.com/PixelyIon/d9c35050af0ef5690566ca9f0965bc32
constexpr u8 SectorWidth{16}; // The width of a sector in bytes
constexpr u8 SectorHeight{2}; // The height of a sector in lines
constexpr u8 GobWidth{64}; // The width of a GOB in bytes
constexpr u8 GobHeight{8}; // The height of a GOB in lines
auto blockHeight{guest->tileConfig.blockHeight}; // The height of the blocks in GOBs
auto robHeight{GobHeight * blockHeight}; // The height of a single ROB (Row of Blocks) in lines
auto surfaceHeight{dimensions.height / guest->format.blockHeight}; // The height of the surface in lines
auto surfaceHeightRobs{util::AlignUp(surfaceHeight, robHeight) / robHeight}; // The height of the surface in ROBs (Row Of Blocks)
auto robWidthBytes{util::AlignUp((guest->tileConfig.surfaceWidth / guest->format.blockWidth) * guest->format.bpb, GobWidth)}; // The width of a ROB in bytes
auto robWidthBlocks{robWidthBytes / GobWidth}; // The width of a ROB in blocks (and GOBs because block width == 1 on the Tegra X1)
auto robBytes{robWidthBytes * robHeight}; // The size of a ROB in bytes
auto gobYOffset{robWidthBytes * GobHeight}; // The offset of the next Y-axis GOB from the current one in linear space
auto blockHeight{guest->tileConfig.blockHeight}; //!< The height of the blocks in GOBs
auto robHeight{GobHeight * blockHeight}; //!< The height of a single ROB (Row of Blocks) in lines
auto surfaceHeight{guest->dimensions.height / guest->format->blockHeight}; //!< The height of the surface in lines
auto surfaceHeightRobs{util::AlignUp(surfaceHeight, robHeight) / robHeight}; //!< The height of the surface in ROBs (Row Of Blocks)
auto robWidthBytes{util::AlignUp((guest->dimensions.width / guest->format->blockWidth) * guest->format->bpb, GobWidth)}; //!< The width of a ROB in bytes
auto robWidthBlocks{robWidthBytes / GobWidth}; //!< The width of a ROB in blocks (and GOBs because block width == 1 on the Tegra X1)
auto robBytes{robWidthBytes * robHeight}; //!< The size of a ROB in bytes
auto gobYOffset{robWidthBytes * GobHeight}; //!< The offset of the next Y-axis GOB from the current one in linear space
auto inputSector{pointer}; // The address of the input sector
auto outputRob{bufferData}; // The address of the output block
auto inputSector{pointer}; //!< The address of the input sector
auto outputRob{bufferData}; //!< The address of the output block
for (u32 rob{}, y{}, paddingY{}; rob < surfaceHeightRobs; rob++) { // Every Surface contains `surfaceHeightRobs` ROBs
auto outputBlock{outputRob}; // We iterate through a block independently of the ROB
@ -195,24 +181,24 @@ namespace skyline::gpu {
blockHeight = static_cast<u8>(std::min(static_cast<u32>(blockHeight), (surfaceHeight - y) / GobHeight)); // Calculate the amount of Y GOBs which aren't padding
paddingY = (guest->tileConfig.blockHeight - blockHeight) * (SectorWidth * SectorWidth * SectorHeight); // Calculate the amount of padding between contiguous sectors
}
} else if (guest->tileMode == texture::TileMode::Pitch) {
auto sizeLine{guest->format.GetSize(dimensions.width, 1)}; // The size of a single line of pixel data
auto sizeStride{guest->format.GetSize(guest->tileConfig.pitch, 1)}; // The size of a single stride of pixel data
} else if (guest->tileConfig.mode == texture::TileMode::Pitch) {
auto sizeLine{guest->format->GetSize(guest->dimensions.width, 1)}; //!< The size of a single line of pixel data
auto sizeStride{guest->format->GetSize(guest->tileConfig.pitch, 1)}; //!< The size of a single stride of pixel data
auto inputLine{pointer}; // The address of the input line
auto outputLine{bufferData}; // The address of the output line
auto inputLine{pointer}; //!< The address of the input line
auto outputLine{bufferData}; //!< The address of the output line
for (u32 line{}; line < dimensions.height; line++) {
for (u32 line{}; line < guest->dimensions.height; line++) {
std::memcpy(outputLine, inputLine, sizeLine);
inputLine += sizeStride;
outputLine += sizeLine;
}
} else if (guest->tileMode == texture::TileMode::Linear) {
} else if (guest->tileConfig.mode == texture::TileMode::Linear) {
std::memcpy(bufferData, pointer, size);
}
if (stagingBuffer) {
if (WaitOnBacking() && size != format.GetSize(dimensions))
if (WaitOnBacking() && size != format->GetSize(dimensions))
throw exception("Backing properties changing during sync is not supported");
WaitOnFence();
@ -269,6 +255,8 @@ namespace skyline::gpu {
void Texture::SynchronizeGuest() {
if (!guest)
throw exception("Synchronization of guest textures requires a valid guest texture to synchronize to");
else if (guest->mappings.size() != 1)
throw exception("Synchronization of non-contigious textures is not supported");
WaitOnBacking();
WaitOnFence();
@ -277,7 +265,7 @@ namespace skyline::gpu {
// TODO: Write Host -> Guest Synchronization
}
void Texture::CopyFrom(std::shared_ptr<Texture> source) {
void Texture::CopyFrom(std::shared_ptr<Texture> source, const vk::ImageSubresourceRange &subresource) {
WaitOnBacking();
WaitOnFence();
@ -302,11 +290,7 @@ namespace skyline::gpu {
.newLayout = vk::ImageLayout::eTransferSrcOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.subresourceRange = {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = 1,
.layerCount = 1,
},
.subresourceRange = subresource,
});
}
@ -320,28 +304,25 @@ namespace skyline::gpu {
.newLayout = vk::ImageLayout::eTransferDstOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.subresourceRange = {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = 1,
.layerCount = 1,
},
.subresourceRange = subresource,
});
if (layout == vk::ImageLayout::eUndefined)
layout = vk::ImageLayout::eTransferDstOptimal;
}
commandBuffer.copyImage(sourceBacking, vk::ImageLayout::eTransferSrcOptimal, destinationBacking, vk::ImageLayout::eTransferDstOptimal, vk::ImageCopy{
.srcSubresource = {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.layerCount = 1,
},
.dstSubresource = {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.layerCount = 1,
},
.extent = dimensions,
});
vk::ImageSubresourceLayers subresourceLayers{
.aspectMask = subresource.aspectMask,
.mipLevel = subresource.baseMipLevel,
.baseArrayLayer = subresource.baseArrayLayer,
.layerCount = subresource.layerCount == VK_REMAINING_ARRAY_LAYERS ? layerCount - subresource.baseArrayLayer : subresource.layerCount,
};
for (; subresourceLayers.mipLevel < (subresource.levelCount == VK_REMAINING_MIP_LEVELS ? mipLevels - subresource.baseMipLevel : subresource.levelCount); subresourceLayers.mipLevel++)
commandBuffer.copyImage(sourceBacking, vk::ImageLayout::eTransferSrcOptimal, destinationBacking, vk::ImageLayout::eTransferDstOptimal, vk::ImageCopy{
.srcSubresource = subresourceLayers,
.dstSubresource = subresourceLayers,
.extent = dimensions,
});
if (layout != vk::ImageLayout::eTransferDstOptimal)
commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, vk::ImageMemoryBarrier{
@ -352,11 +333,7 @@ namespace skyline::gpu {
.newLayout = layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.subresourceRange = {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = 1,
.layerCount = 1,
},
.subresourceRange = subresource,
});
if (layout != vk::ImageLayout::eTransferSrcOptimal)
@ -368,13 +345,38 @@ namespace skyline::gpu {
.newLayout = source->layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.subresourceRange = {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = 1,
.layerCount = 1,
},
.subresourceRange = subresource,
});
});
cycle->AttachObjects(source, shared_from_this());
cycle->AttachObjects(std::move(source), shared_from_this());
}
TextureView::TextureView(std::shared_ptr<Texture> backing, vk::ImageViewType type, vk::ImageSubresourceRange range, texture::Format format, vk::ComponentMapping mapping) : backing(std::move(backing)), type(type), format(format), mapping(mapping), range(range) {}
vk::ImageView TextureView::GetView() {
/*
if (view)
return **view;
auto viewType{[&]() {
switch (backing->dimensions.GetType()) {
case vk::ImageType::e1D:
return range.layerCount > 1 ? vk::ImageViewType::e1DArray : vk::ImageViewType::e1D;
case vk::ImageType::e2D:
return range.layerCount > 1 ? vk::ImageViewType::e2DArray : vk::ImageViewType::e2D;
case vk::ImageType::e3D:
return vk::ImageViewType::e3D;
}
}()};
return *view.emplace(backing->gpu.vkDevice, vk::ImageViewCreateInfo{
.image = backing->GetBacking(),
.viewType = vk::ImageViewType::eCube,
.format = format ? *format : *backing->format,
.components = mapping,
.subresourceRange = range,
});
*/
throw exception("TODO: TextureView::GetView");
}
}

View File

@ -3,7 +3,7 @@
#pragma once
#include <gpu/fence_cycle.h>
#include <gpu/memory_manager.h>
namespace skyline::gpu {
namespace texture {
@ -27,9 +27,9 @@ namespace skyline::gpu {
auto operator<=>(const Dimensions &) const = default;
constexpr vk::ImageType GetType() const {
if (depth)
if (depth > 1)
return vk::ImageType::e3D;
else if (width)
else if (height > 1)
return vk::ImageType::e2D;
else
return vk::ImageType::e1D;
@ -61,11 +61,12 @@ namespace skyline::gpu {
/**
* @note Blocks refers to the atomic unit of a compressed format (IE: The minimum amount of data that can be decompressed)
*/
struct Format {
struct FormatBase {
u8 bpb{}; //!< Bytes Per Block, this is used instead of bytes per pixel as that might not be a whole number for compressed formats
u16 blockHeight{}; //!< The height of a block in pixels
u16 blockWidth{}; //!< The width of a block in pixels
vk::Format vkFormat{vk::Format::eUndefined};
vk::ImageAspectFlags vkAspect{vk::ImageAspectFlagBits::eColor};
constexpr bool IsCompressed() const {
return (blockHeight != 1) || (blockWidth != 1);
@ -85,11 +86,11 @@ namespace skyline::gpu {
return GetSize(dimensions.width, dimensions.height, dimensions.depth);
}
constexpr bool operator==(const Format &format) const {
constexpr bool operator==(const FormatBase &format) const {
return vkFormat == format.vkFormat;
}
constexpr bool operator!=(const Format &format) const {
constexpr bool operator!=(const FormatBase &format) const {
return vkFormat != format.vkFormat;
}
@ -103,6 +104,38 @@ namespace skyline::gpu {
constexpr operator bool() const {
return bpb;
}
/**
* @return If the supplied format is texel-layout compatible with the current format
*/
constexpr bool IsCompatible(const FormatBase &other) const {
return bpb == other.bpb && blockHeight == other.blockHeight && blockWidth == other.blockWidth;
}
};
/**
* @brief A wrapper around a pointer to underlying format metadata to prevent redundant copies
*/
class Format {
private:
const FormatBase *base;
public:
constexpr Format(const FormatBase &base) : base(&base) {}
constexpr Format() : base(nullptr) {}
constexpr const FormatBase *operator->() const {
return base;
}
constexpr const FormatBase &operator*() const {
return *base;
}
constexpr operator bool() const {
return base;
}
};
/**
@ -118,13 +151,26 @@ namespace skyline::gpu {
/**
* @brief The parameters of the tiling mode, covered in Table 76 in the Tegra X1 TRM
*/
union TileConfig {
struct {
u8 blockHeight; //!< The height of the blocks in GOBs
u8 blockDepth; //!< The depth of the blocks in GOBs
u16 surfaceWidth; //!< The width of a surface in samples
struct TileConfig {
TileMode mode;
union {
struct {
u8 blockHeight; //!< The height of the blocks in GOBs
u8 blockDepth; //!< The depth of the blocks in GOBs
};
u32 pitch; //!< The pitch of the texture if it's pitch linear
};
u32 pitch; //!< The pitch of the texture if it's pitch linear
constexpr bool operator==(const TileConfig &other) const {
if (mode == other.mode)
if (mode == TileMode::Linear)
return true;
else if (mode == TileMode::Pitch)
return pitch == other.pitch;
else if (mode == TileMode::Block)
return blockHeight == other.blockHeight && blockDepth == other.blockDepth;
return false;
}
};
enum class SwizzleChannel : u8 {
@ -168,56 +214,73 @@ namespace skyline::gpu {
};
}
};
/**
* @brief The type of a texture to determine the access patterns for it
* @note This is effectively the Tegra X1 texture types with the 1DBuffer + 2DNoMipmap removed as those are handled elsewhere
* @note We explicitly utilize Vulkan types here as it provides the most efficient conversion while not exposing Vulkan to the outer API
*/
enum class TextureType {
e1D = VK_IMAGE_VIEW_TYPE_1D,
e2D = VK_IMAGE_VIEW_TYPE_2D,
e3D = VK_IMAGE_VIEW_TYPE_3D,
eCube = VK_IMAGE_VIEW_TYPE_CUBE,
e1DArray = VK_IMAGE_VIEW_TYPE_1D_ARRAY,
e2DArray = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
eCubeArray = VK_IMAGE_VIEW_TYPE_CUBE_ARRAY,
};
}
class Texture;
class PresentationEngine; //!< A forward declaration of PresentationEngine as we require it to be able to create a Texture object
/**
* @brief A texture present in guest memory, it can be used to create a corresponding Texture object for usage on the host
* @brief A descriptor for a texture present in guest memory, it can be used to create a corresponding Texture object for usage on the host
*/
class GuestTexture : public std::enable_shared_from_this<GuestTexture> {
private:
const DeviceState &state;
struct GuestTexture {
using Mappings = boost::container::small_vector<span < u8>, 3>;
public:
u8 *pointer; //!< The address of the texture in guest memory
std::weak_ptr<Texture> host; //!< A host texture (if any) that was created from this guest texture
Mappings mappings; //!< Spans to CPU memory for the underlying data backing this texture
texture::Dimensions dimensions;
texture::Format format;
texture::TileMode tileMode;
texture::TileConfig tileConfig;
texture::TextureType type;
u16 baseArrayLayer;
u16 layerCount;
u32 layerStride; //!< An optional hint regarding the size of a single layer, it will be set to 0 when not available
GuestTexture(const DeviceState &state, u8 *pointer, texture::Dimensions dimensions, const texture::Format &format, texture::TileMode tileMode = texture::TileMode::Linear, texture::TileConfig tileConfig = {});
GuestTexture() {}
constexpr size_t Size() {
return format.GetSize(dimensions);
}
GuestTexture(Mappings mappings, texture::Dimensions dimensions, texture::Format format, texture::TileConfig tileConfig, texture::TextureType type, u16 baseArrayLayer = 0, u16 layerCount = 1, u32 layerStride = 0) : mappings(mappings), dimensions(dimensions), format(format), tileConfig(tileConfig), type(type), baseArrayLayer(baseArrayLayer), layerCount(layerCount), layerStride(layerStride) {}
GuestTexture(span <u8> mapping, texture::Dimensions dimensions, texture::Format format, texture::TileConfig tileConfig, texture::TextureType type, u16 baseArrayLayer = 0, u16 layerCount = 1, u32 layerStride = 0) : mappings(1, mapping), dimensions(dimensions), format(format), tileConfig(tileConfig), type(type), baseArrayLayer(baseArrayLayer), layerCount(layerCount), layerStride(layerStride) {}
};
class TextureManager;
/**
* @brief A view into a specific subresource of a Texture
*/
class TextureView {
private:
vk::raii::ImageView *view{};
public:
std::shared_ptr<Texture> backing;
vk::ImageViewType type;
texture::Format format;
vk::ComponentMapping mapping;
vk::ImageSubresourceRange range;
/**
* @brief Creates a corresponding host texture object for this guest texture
* @param backing The Vulkan Image that is used as the backing on the host, its lifetime is not managed by the host texture object
* @param dimensions The dimensions of the host texture (Defaults to the dimensions of the host texture)
* @param format The format of the host texture (Defaults to the format of the guest texture)
* @param tiling The tiling used by the image on host, this is the same as guest by default
* @param layout The initial layout of the Vulkan Image, this is used for efficient layout management
* @param swizzle The channel swizzle of the host texture (Defaults to no channel swizzling)
* @return A shared pointer to the host texture object
* @note There can only be one host texture for a corresponding guest texture
* @note If any of the supplied parameters do not match up with the backing then it's undefined behavior
* @param format A compatible format for the texture view (Defaults to the format of the backing texture)
*/
std::shared_ptr<Texture> InitializeTexture(vk::Image backing, texture::Dimensions dimensions = {}, const texture::Format &format = {}, std::optional<vk::ImageTiling> tiling = std::nullopt, vk::ImageLayout layout = vk::ImageLayout::eUndefined, texture::Swizzle swizzle = {});
TextureView(std::shared_ptr<Texture> backing, vk::ImageViewType type, vk::ImageSubresourceRange range, texture::Format format = {}, vk::ComponentMapping mapping = {});
/**
* @note As a RAII object is used here, the lifetime of the backing is handled by the host texture
* @return A Vulkan Image View that corresponds to the properties of this view
*/
std::shared_ptr<Texture> InitializeTexture(vk::raii::Image &&backing, std::optional<vk::ImageTiling> tiling = std::nullopt, vk::ImageLayout layout = vk::ImageLayout::eUndefined, const texture::Format &format = {}, texture::Dimensions dimensions = {}, texture::Swizzle swizzle = {});
/**
* @brief Similar to InitializeTexture but creation of the backing and allocation of memory for the backing is automatically performed by the function
* @param usage Usage flags that will applied aside from VK_IMAGE_USAGE_TRANSFER_SRC_BIT/VK_IMAGE_USAGE_TRANSFER_DST_BIT which are mandatory
*/
std::shared_ptr<Texture> CreateTexture(vk::ImageUsageFlags usage = {}, std::optional<vk::ImageTiling> tiling = std::nullopt, vk::ImageLayout initialLayout = vk::ImageLayout::eGeneral, const texture::Format &format = {}, texture::Dimensions dimensions = {}, texture::Swizzle swizzle = {});
vk::ImageView GetView();
};
/**
@ -232,7 +295,30 @@ namespace skyline::gpu {
using BackingType = std::variant<vk::Image, vk::raii::Image, memory::Image>;
BackingType backing; //!< The Vulkan image that backs this texture, it is nullable
std::shared_ptr<FenceCycle> cycle; //!< A fence cycle for when any host operation mutating the texture has completed, it must be waited on prior to any mutations to the backing
friend TextureManager;
public:
std::optional<GuestTexture> guest;
texture::Dimensions dimensions;
texture::Format format;
vk::ImageLayout layout;
vk::ImageTiling tiling;
u32 mipLevels;
u32 layerCount; //!< The amount of array layers in the image, utilized for efficient binding (Not to be confused with the depth or faces in a cubemap)
vk::SampleCountFlagBits sampleCount;
Texture(GPU &gpu, BackingType &&backing, GuestTexture guest, texture::Dimensions dimensions, texture::Format format, vk::ImageLayout layout, vk::ImageTiling tiling, u32 mipLevels = 1, u32 layerCount = 1, vk::SampleCountFlagBits sampleCount = vk::SampleCountFlagBits::e1);
Texture(GPU &gpu, BackingType &&backing, texture::Dimensions dimensions, texture::Format format, vk::ImageLayout layout, vk::ImageTiling tiling, u32 mipLevels = 1, u32 layerCount = 1, vk::SampleCountFlagBits sampleCount = vk::SampleCountFlagBits::e1);
Texture(GPU &gpu, GuestTexture guest);
/**
* @brief Creates and allocates memory for the backing to creates a texture object wrapping it
* @param usage Usage flags that will applied aside from VK_IMAGE_USAGE_TRANSFER_SRC_BIT/VK_IMAGE_USAGE_TRANSFER_DST_BIT which are mandatory
*/
Texture(GPU &gpu, texture::Dimensions dimensions, texture::Format format, vk::ImageLayout initialLayout = vk::ImageLayout::eGeneral, vk::ImageUsageFlags usage = {}, vk::ImageTiling tiling = vk::ImageTiling::eOptimal, u32 mipLevels = 1, u32 layerCount = 1, vk::SampleCountFlagBits sampleCount = vk::SampleCountFlagBits::e1);
/**
* @note The handle returned is nullable and the appropriate precautions should be taken
@ -245,23 +331,6 @@ namespace skyline::gpu {
}, backing);
}
public:
std::shared_ptr<GuestTexture> guest; //!< The guest texture from which this was created, it's required for syncing
texture::Dimensions dimensions;
texture::Format format;
vk::ImageTiling tiling;
vk::ComponentMapping mapping;
Texture(GPU &gpu, BackingType &&backing, std::shared_ptr<GuestTexture> guest, texture::Dimensions dimensions, const texture::Format &format, vk::ImageLayout layout, vk::ImageTiling tiling, vk::ComponentMapping mapping);
Texture(GPU &gpu, BackingType &&backing, texture::Dimensions dimensions, const texture::Format &format, vk::ImageLayout layout, vk::ImageTiling tiling, vk::ComponentMapping mapping = {});
/**
* @brief Creates and allocates memory for the backing to creates a texture object wrapping it
* @param usage Usage flags that will applied aside from VK_IMAGE_USAGE_TRANSFER_SRC_BIT/VK_IMAGE_USAGE_TRANSFER_DST_BIT which are mandatory
*/
Texture(GPU &gpu, texture::Dimensions dimensions, const texture::Format &format, vk::ImageLayout initialLayout = vk::ImageLayout::eGeneral, vk::ImageUsageFlags usage = {}, vk::ImageTiling tiling = vk::ImageTiling::eOptimal, vk::ComponentMapping mapping = {});
/**
* @brief Acquires an exclusive lock on the texture for the calling thread
* @note Naming is in accordance to the BasicLockable named requirement
@ -311,28 +380,11 @@ namespace skyline::gpu {
*/
void TransitionLayout(vk::ImageLayout layout);
/**
* @brief Convert this texture to the specified tiling mode
* @param tileMode The tiling mode to convert it to
* @param tileConfig The configuration for the tiling mode (Can be default argument for Linear)
*/
void ConvertTileMode(texture::TileMode tileMode, texture::TileConfig tileConfig = {});
/**
* @brief Converts the texture dimensions to the specified ones (As long as they are within the GuestTexture's range)
*/
void SetDimensions(texture::Dimensions dimensions);
/**
* @brief Converts the texture to have the specified format
*/
void SetFormat(texture::Format format);
/**
* @brief Change the texture channel swizzle to the specified one
*/
void SetSwizzle(texture::Swizzle swizzle);
/**
* @brief Synchronizes the host texture with the guest after it has been modified
* @note The texture **must** be locked prior to calling this
@ -350,6 +402,10 @@ namespace skyline::gpu {
/**
* @brief Copies the contents of the supplied source texture into the current texture
*/
void CopyFrom(std::shared_ptr<Texture> source);
void CopyFrom(std::shared_ptr<Texture> source, const vk::ImageSubresourceRange &subresource = vk::ImageSubresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = VK_REMAINING_MIP_LEVELS,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
});
};
}

View File

@ -0,0 +1,84 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include "texture_manager.h"
namespace skyline::gpu {
TextureManager::TextureManager(GPU &gpu) : gpu(gpu) {}
TextureView TextureManager::FindOrCreate(const GuestTexture &guestTexture) {
auto guestMapping{guestTexture.mappings.front()};
// Iterate over all textures that overlap with the first mapping of the guest texture and compare the mappings:
// 1) All mappings match up perfectly, we check that the rest of the supplied mappings correspond to mappings in the texture
// 1.1) If they match as well, we check for format/dimensions/tiling config matching the texture and return or move onto (3)
// 2) Only a contiguous range of mappings match, we check for if the overlap is meaningful with layout math, it can go two ways:
// 2.1) If there is a meaningful overlap, we check for format/dimensions/tiling config compatibility and return or move onto (3)
// 2.2) If there isn't, we move onto (3)
// 3) If there's another overlap we go back to (1) with it else we go to (4)
// 4) We check all the overlapping texture for if they're in the texture pool:
// 4.1) If they are, we do nothing to them
// 4.2) If they aren't, we delete them from the map
// 5) Create a new texture and insert it in the map then return it
std::shared_ptr<Texture> match{};
auto mappingEnd{std::upper_bound(textures.begin(), textures.end(), guestMapping)}, hostMapping{mappingEnd};
while (hostMapping != textures.begin() && std::prev(hostMapping)->end() > guestMapping.begin()) {
auto &hostMappings{hostMapping->texture->guest->mappings};
// We need to check that all corresponding mappings in the candidate texture and the guest texture match up
// Only the start of the first matched mapping and the end of the last mapping can not match up as this is the case for views
auto firstHostMapping{hostMapping->iterator};
auto lastGuestMapping{guestTexture.mappings.back()};
auto lastHostMapping{std::find_if(firstHostMapping, hostMappings.end(), [&lastGuestMapping](const span<u8> &it) {
return lastGuestMapping.begin() >= it.begin() && lastGuestMapping.size() <= it.size();
})};
bool mappingMatch{std::equal(firstHostMapping, lastHostMapping, guestTexture.mappings.begin(), guestTexture.mappings.end(), [](const span<u8> &lhs, const span<u8> &rhs) {
return lhs.end() == rhs.end(); // We check end() here to implicitly ignore any offset from the first mapping
})};
if (firstHostMapping == hostMappings.begin() && firstHostMapping->begin() == guestMapping.begin() && mappingMatch && lastHostMapping == std::prev(hostMappings.end()) && lastGuestMapping.end() == lastHostMapping->end()) {
// We've gotten a perfect 1:1 match for *all* mappings from the start to end, we just need to check for compatibility aside from this
auto &matchGuestTexture{*hostMapping->texture->guest};
if (matchGuestTexture.format->IsCompatible(*guestTexture.format) && matchGuestTexture.dimensions == guestTexture.dimensions && matchGuestTexture.tileConfig == guestTexture.tileConfig) {
auto &texture{hostMapping->texture};
return TextureView(texture, static_cast<vk::ImageViewType>(guestTexture.type), vk::ImageSubresourceRange{
.aspectMask = guestTexture.format->vkAspect,
.levelCount = texture->mipLevels,
.layerCount = texture->layerCount,
}, guestTexture.format);
}
} else if (mappingMatch) {
// We've gotten a partial match with a certain subset of contiguous mappings matching, we need to check if this is a meaningful overlap
if (false) {
// TODO: Layout Checks + Check match against Base Layer in TIC
auto &texture{hostMapping->texture};
return TextureView(texture, static_cast<vk::ImageViewType>(guestTexture.type), vk::ImageSubresourceRange{
.aspectMask = guestTexture.format->vkAspect,
.levelCount = texture->mipLevels,
.layerCount = texture->layerCount,
}, guestTexture.format);
}
}
}
// Create a texture as we cannot find one that matches
auto texture{std::make_shared<Texture>(gpu, guestTexture)};
auto it{texture->guest->mappings.begin()};
textures.emplace(mappingEnd, TextureMapping{texture, it, guestMapping});
while ((++it) != texture->guest->mappings.end()) {
guestMapping = *it;
mappingEnd = hostMapping = std::upper_bound(textures.begin(), textures.end(), guestMapping);
while (hostMapping != textures.begin() && std::prev(hostMapping)->end() > guestMapping.begin()) {
// TODO: Delete textures not in texture pool
}
textures.emplace(mappingEnd, TextureMapping{texture, it, guestMapping});
}
return TextureView(texture, static_cast<vk::ImageViewType>(guestTexture.type), vk::ImageSubresourceRange{
.aspectMask = guestTexture.format->vkAspect,
.levelCount = texture->mipLevels,
.layerCount = texture->layerCount,
}, guestTexture.format);
}
}

View File

@ -0,0 +1,42 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include "texture/texture.h"
#include <random>
namespace skyline::gpu {
/**
* @brief The Texture Manager is responsible for maintaining a global view of textures being mapped from the guest to the host, any lookups and creation of host texture from equivalent guest textures alongside reconciliation of any overlaps with existing textures
*/
class TextureManager {
private:
/**
* @brief A single contiguous mapping of a texture in the CPU address space
*/
struct TextureMapping : span<u8> {
std::shared_ptr<Texture> texture;
GuestTexture::Mappings::iterator iterator; //!< An iterator to the mapping in the texture's GuestTexture corresponding to this mapping
template<typename... Args>
TextureMapping(std::shared_ptr<Texture> texture, GuestTexture::Mappings::iterator iterator, Args &&... args) : span<u8>(std::forward<Args>(args)...), texture(std::move(texture)), iterator(iterator) {}
};
GPU &gpu;
std::mutex mutex; //!< Synchronizes access to the texture mappings
std::vector<TextureMapping> textures; //!< A sorted vector of all texture mappings
bool IsSizeCompatible(texture::Dimensions lhsDimension, texture::TileConfig lhsConfig, texture::Dimensions rhsDimension, texture::TileConfig rhsConfig) {
return lhsDimension == rhsDimension && lhsConfig == rhsConfig;
}
public:
TextureManager(GPU &gpu);
/**
* @return A pre-existing or newly created Texture object which matches the specified criteria
*/
TextureView FindOrCreate(const GuestTexture &guestTexture);
};
}