From 795472004e920efe034ae0e1b7cb43652b616cb1 Mon Sep 17 00:00:00 2001 From: PixelyIon Date: Sun, 4 Apr 2021 16:39:32 +0530 Subject: [PATCH] Presentation Engine Vulkan Surface + Swapchain Initialization This commit adds in VkSurface/VkSwapchain initialization and recreation. It also adapts GraphicsBuffferProducer and Texture to fit in with those changes but it doesn't yet implement presenting those buffers nor uploading guest buffers onto the host. --- app/src/main/cpp/skyline/gpu.cpp | 20 +- .../cpp/skyline/gpu/presentation_engine.cpp | 104 ++++-- .../cpp/skyline/gpu/presentation_engine.h | 39 +- app/src/main/cpp/skyline/gpu/texture.cpp | 13 +- app/src/main/cpp/skyline/gpu/texture.h | 341 +++++++++--------- .../hosbinder/GraphicBufferProducer.cpp | 39 +- .../hosbinder/GraphicBufferProducer.h | 9 +- 7 files changed, 317 insertions(+), 248 deletions(-) diff --git a/app/src/main/cpp/skyline/gpu.cpp b/app/src/main/cpp/skyline/gpu.cpp index 15a23a96..53135f1a 100644 --- a/app/src/main/cpp/skyline/gpu.cpp +++ b/app/src/main/cpp/skyline/gpu.cpp @@ -40,10 +40,15 @@ namespace skyline::gpu { } #ifdef NDEBUG - constexpr std::array requiredInstanceExtensions{}; + constexpr std::array requiredInstanceExtensions{ + VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, + }; #else - constexpr std::array requiredInstanceExtensions{ - VK_EXT_DEBUG_REPORT_EXTENSION_NAME + constexpr std::array requiredInstanceExtensions{ + VK_EXT_DEBUG_REPORT_EXTENSION_NAME, + VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, }; #endif @@ -98,7 +103,7 @@ namespace skyline::gpu { return std::move(vk::raii::PhysicalDevices(instance).front()); // We just select the first device as we aren't expecting multiple GPUs } - vk::raii::Device GPU::CreateDevice(const DeviceState &state, const vk::raii::PhysicalDevice &physicalDevice, typeof(vk::DeviceQueueCreateInfo::queueCount)& vkQueueFamilyIndex) { + vk::raii::Device GPU::CreateDevice(const DeviceState &state, const vk::raii::PhysicalDevice &physicalDevice, typeof(vk::DeviceQueueCreateInfo::queueCount) &vkQueueFamilyIndex) { auto properties{physicalDevice.getProperties()}; // We should check for required properties here, if/when we have them // auto features{physicalDevice.getFeatures()}; // Same as above @@ -135,8 +140,7 @@ namespace skyline::gpu { throw exception("Cannot find a queue family with both eGraphics and eCompute bits set"); }()}; - - if (state.logger->configLevel >= Logger::LogLevel::Error) { + if (state.logger->configLevel >= Logger::LogLevel::Debug) { std::string extensionString; for (const auto &extension : deviceExtensions) extensionString += util::Format("\n* {} (v{}.{}.{})", extension.extensionName, VK_VERSION_MAJOR(extension.specVersion), VK_VERSION_MINOR(extension.specVersion), VK_VERSION_PATCH(extension.specVersion)); @@ -146,7 +150,7 @@ namespace skyline::gpu { for (const auto &queueFamily : queueFamilies) queueString += util::Format("\n* {}x{}{}{}{}{}: TSB{} MIG({}x{}x{}){}", queueFamily.queueCount, queueFamily.queueFlags & vk::QueueFlagBits::eGraphics ? 'G' : '-', queueFamily.queueFlags & vk::QueueFlagBits::eCompute ? 'C' : '-', queueFamily.queueFlags & vk::QueueFlagBits::eTransfer ? 'T' : '-', queueFamily.queueFlags & vk::QueueFlagBits::eSparseBinding ? 'S' : '-', queueFamily.queueFlags & vk::QueueFlagBits::eProtected ? 'P' : '-', queueFamily.timestampValidBits, queueFamily.minImageTransferGranularity.width, queueFamily.minImageTransferGranularity.height, queueFamily.minImageTransferGranularity.depth, familyIndex++ == vkQueueFamilyIndex ? " <--" : ""); - state.logger->Error("Vulkan Device:\nName: {}\nType: {}\nVulkan Version: {}.{}.{}\nDriver Version: {}.{}.{}\nQueues:{}\nExtensions:{}", properties.deviceName, vk::to_string(properties.deviceType), VK_VERSION_MAJOR(properties.apiVersion), VK_VERSION_MINOR(properties.apiVersion), VK_VERSION_PATCH(properties.apiVersion), VK_VERSION_MAJOR(properties.driverVersion), VK_VERSION_MINOR(properties.driverVersion), VK_VERSION_PATCH(properties.driverVersion), queueString, extensionString); + state.logger->Debug("Vulkan Device:\nName: {}\nType: {}\nVulkan Version: {}.{}.{}\nDriver Version: {}.{}.{}\nQueues:{}\nExtensions:{}", properties.deviceName, vk::to_string(properties.deviceType), VK_VERSION_MAJOR(properties.apiVersion), VK_VERSION_MINOR(properties.apiVersion), VK_VERSION_PATCH(properties.apiVersion), VK_VERSION_MAJOR(properties.driverVersion), VK_VERSION_MINOR(properties.driverVersion), VK_VERSION_PATCH(properties.driverVersion), queueString, extensionString); } return vk::raii::Device(physicalDevice, vk::DeviceCreateInfo{ @@ -157,5 +161,5 @@ namespace skyline::gpu { }); } - GPU::GPU(const DeviceState &state) : vkInstance(CreateInstance(state, vkContext)), vkDebugReportCallback(CreateDebugReportCallback(state, vkInstance)), vkPhysicalDevice(CreatePhysicalDevice(state, vkInstance)), vkDevice(CreateDevice(state, vkPhysicalDevice, vkQueueFamilyIndex)), vkQueue(vkDevice, vkQueueFamilyIndex, 0), presentation(state) {} + GPU::GPU(const DeviceState &state) : vkInstance(CreateInstance(state, vkContext)), vkDebugReportCallback(CreateDebugReportCallback(state, vkInstance)), vkPhysicalDevice(CreatePhysicalDevice(state, vkInstance)), vkDevice(CreateDevice(state, vkPhysicalDevice, vkQueueFamilyIndex)), vkQueue(vkDevice, vkQueueFamilyIndex, 0), presentation(state, *this) {} } diff --git a/app/src/main/cpp/skyline/gpu/presentation_engine.cpp b/app/src/main/cpp/skyline/gpu/presentation_engine.cpp index d5ab20da..af6fbcfb 100644 --- a/app/src/main/cpp/skyline/gpu/presentation_engine.cpp +++ b/app/src/main/cpp/skyline/gpu/presentation_engine.cpp @@ -2,6 +2,7 @@ // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) #include +#include #include "jvm.h" #include "presentation_engine.h" @@ -9,22 +10,57 @@ extern skyline::i32 Fps; extern skyline::i32 FrameTime; namespace skyline::gpu { - PresentationEngine::PresentationEngine(const DeviceState &state) : state(state), vsyncEvent(std::make_shared(state, true)), bufferEvent(std::make_shared(state, true)), presentationTrack(static_cast(trace::TrackIds::Presentation), perfetto::ProcessTrack::Current()) { + PresentationEngine::PresentationEngine(const DeviceState &state, const GPU &gpu) : state(state), gpu(gpu), vsyncEvent(std::make_shared(state, true)), bufferEvent(std::make_shared(state, true)), presentationTrack(static_cast(trace::TrackIds::Presentation), perfetto::ProcessTrack::Current()) { auto desc{presentationTrack.Serialize()}; desc.set_name("Presentation"); perfetto::TrackEvent::SetTrackDescriptor(presentationTrack, desc); } PresentationEngine::~PresentationEngine() { - if (window) - ANativeWindow_release(window); auto env{state.jvm->GetEnv()}; if (!env->IsSameObject(surface, nullptr)) env->DeleteGlobalRef(surface); } + void PresentationEngine::UpdateSwapchain(u32 imageCount, vk::Format imageFormat, vk::Extent2D imageExtent) { + if (!imageCount) + return; + + auto capabilities{gpu.vkPhysicalDevice.getSurfaceCapabilitiesKHR(**vkSurface)}; + if (imageCount < capabilities.minImageCount || (capabilities.maxImageCount && imageCount > capabilities.maxImageCount)) + throw exception("Cannot update swapchain to accomodate image count: {} ({}-{})", imageCount, capabilities.minImageCount, capabilities.maxImageCount); + if (capabilities.minImageExtent.height > imageExtent.height || capabilities.minImageExtent.width > imageExtent.width || capabilities.maxImageExtent.height < imageExtent.height || capabilities.maxImageExtent.width < imageExtent.width) + throw exception("Cannot update swapchain to accomodate image extent: {}x{} ({}x{}-{}x{})", imageExtent.width, imageExtent.height, capabilities.minImageExtent.width, capabilities.minImageExtent.height, capabilities.maxImageExtent.width, capabilities.maxImageExtent.height); + + constexpr vk::ImageUsageFlags presentUsage{vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst}; + if ((capabilities.supportedUsageFlags & presentUsage) != presentUsage) + throw exception("Swapchain doesn't support image usage '{}': {}", vk::to_string(presentUsage), vk::to_string(capabilities.supportedUsageFlags)); + + vkSwapchain = vk::raii::SwapchainKHR(gpu.vkDevice, vk::SwapchainCreateInfoKHR{ + .surface = **vkSurface, + .minImageCount = imageCount, + .imageFormat = imageFormat, + .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, + .imageExtent = imageExtent, + .imageArrayLayers = 1, + .imageUsage = presentUsage, + .imageSharingMode = vk::SharingMode::eExclusive, + .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eInherit, + .presentMode = vk::PresentModeKHR::eFifo, + .clipped = false, + .oldSwapchain = vkSwapchain ? **vkSwapchain : vk::SwapchainKHR{}, + }); + + swapchain = SwapchainContext{ + .imageCount = imageCount, + .imageFormat = imageFormat, + .imageExtent = imageExtent, + }; + } + void PresentationEngine::UpdateSurface(jobject newSurface) { - std::lock_guard lock(windowMutex); + std::lock_guard lock(mutex); + auto env{state.jvm->GetEnv()}; if (!env->IsSameObject(surface, nullptr)) { env->DeleteGlobalRef(surface); @@ -32,51 +68,47 @@ namespace skyline::gpu { } if (!env->IsSameObject(newSurface, nullptr)) surface = env->NewGlobalRef(newSurface); + if (surface) { - window = ANativeWindow_fromSurface(env, surface); - ANativeWindow_acquire(window); - resolution.width = static_cast(ANativeWindow_getWidth(window)); - resolution.height = static_cast(ANativeWindow_getHeight(window)); - format = ANativeWindow_getFormat(window); - windowConditional.notify_all(); + vkSurface.emplace(gpu.vkInstance, vk::AndroidSurfaceCreateInfoKHR{ + .window = ANativeWindow_fromSurface(env, surface), + }); + if (!gpu.vkPhysicalDevice.getSurfaceSupportKHR(gpu.vkQueueFamilyIndex, **vkSurface)) + throw exception("Vulkan Queue doesn't support presentation with surface"); + + UpdateSwapchain(swapchain.imageCount, swapchain.imageFormat, swapchain.imageExtent); + + surfaceCondition.notify_all(); } else { - window = nullptr; + vkSurface.reset(); } } - void PresentationEngine::Present(const std::shared_ptr &texture) { - std::unique_lock lock(windowMutex); - if (!window) - windowConditional.wait(lock, [this]() { return window; }); + std::shared_ptr PresentationEngine::CreatePresentationTexture(const std::shared_ptr &texture, u32 slot) { + std::unique_lock lock(mutex); + if (swapchain.imageCount <= slot) + UpdateSwapchain(slot + 1, texture->format.vkFormat, texture->dimensions); + return texture->InitializeTexture(vk::raii::Image(gpu.vkDevice, vkSwapchain->getImages().at(slot))); + } - auto textureFormat{[&texture]() { - switch (texture->format.vkFormat) { - case vk::Format::eR8G8B8A8Unorm: - return WINDOW_FORMAT_RGBA_8888; - case vk::Format::eR5G6B5UnormPack16: - return WINDOW_FORMAT_RGB_565; - default: - throw exception("Cannot find corresponding Android surface format"); - } - }()}; - if (resolution != texture->dimensions || textureFormat != format) { - ANativeWindow_setBuffersGeometry(window, texture->dimensions.width, texture->dimensions.height, textureFormat); - resolution = texture->dimensions; - format = textureFormat; + u32 PresentationEngine::GetFreeTexture() { + std::unique_lock lock(mutex); + auto nextImage{vkSwapchain->acquireNextImage(std::numeric_limits::max())}; + if (nextImage.first == vk::Result::eErrorSurfaceLostKHR || nextImage.first == vk::Result::eSuboptimalKHR) { + surfaceCondition.wait(lock, [this]() { return vkSurface.has_value(); }); + return GetFreeTexture(); } + return nextImage.second; + } - ANativeWindow_Buffer buffer; - ARect rect; - - ANativeWindow_lock(window, &buffer, &rect); - std::memcpy(buffer.bits, texture->backing.data(), texture->backing.size()); - ANativeWindow_unlockAndPost(window); + void PresentationEngine::Present(const std::shared_ptr &texture) { + std::unique_lock lock(mutex); + surfaceCondition.wait(lock, [this]() { return vkSurface.has_value(); }); vsyncEvent->Signal(); if (frameTimestamp) { auto now{util::GetTimeNs()}; - FrameTime = static_cast((now - frameTimestamp) / 10000); // frametime / 100 is the real ms value, this is to retain the first two decimals Fps = static_cast(constant::NsInSecond / (now - frameTimestamp)); diff --git a/app/src/main/cpp/skyline/gpu/presentation_engine.h b/app/src/main/cpp/skyline/gpu/presentation_engine.h index 41b3503d..a20b46a1 100644 --- a/app/src/main/cpp/skyline/gpu/presentation_engine.h +++ b/app/src/main/cpp/skyline/gpu/presentation_engine.h @@ -10,29 +10,58 @@ struct ANativeWindow; namespace skyline::gpu { + /** + * @brief All host presentation is handled by this, it manages the host surface and swapchain alongside dynamically recreating it when required + */ class PresentationEngine { private: const DeviceState &state; - std::mutex windowMutex; - std::condition_variable windowConditional; + const GPU &gpu; + std::mutex mutex; //!< Synchronizes access to the surface objects + std::condition_variable surfaceCondition; //!< Allows us to efficiently wait for the window object to be set jobject surface{}; //!< The Surface object backing the ANativeWindow + + std::optional vkSurface; //!< The Vulkan Surface object that is backed by ANativeWindow + std::optional vkSwapchain; //!< The Vulkan swapchain and the properties associated with it + struct SwapchainContext { + u32 imageCount{}; + vk::Format imageFormat{}; + vk::Extent2D imageExtent{}; + } swapchain; //!< The properties of the currently created swapchain + u64 frameTimestamp{}; //!< The timestamp of the last frame being shown perfetto::Track presentationTrack; //!< Perfetto track used for presentation events + void UpdateSwapchain(u32 imageCount, vk::Format imageFormat, vk::Extent2D imageExtent); + public: texture::Dimensions resolution{}; i32 format{}; std::shared_ptr vsyncEvent; //!< Signalled every time a frame is drawn std::shared_ptr bufferEvent; //!< Signalled every time a buffer is freed - PresentationEngine(const DeviceState &state); + PresentationEngine(const DeviceState &state, const GPU& gpu); ~PresentationEngine(); + /** + * @brief Replaces the underlying Android surface with a new one, it handles resetting the swapchain and such + */ void UpdateSurface(jobject newSurface); - void Present(const std::shared_ptr &texture); + /** + * @brief Creates a Texture object from a GuestTexture as a part of the Vulkan swapchain + */ + std::shared_ptr CreatePresentationTexture(const std::shared_ptr &texture, u32 slot); - ANativeWindow *window{}; + /** + * @return The slot of the texture that's available to write into + */ + u32 GetFreeTexture(); + + /** + * @brief Send the supplied texture to the presentation queue to be displayed + */ + void Present(const std::shared_ptr &texture); }; } diff --git a/app/src/main/cpp/skyline/gpu/texture.cpp b/app/src/main/cpp/skyline/gpu/texture.cpp index 6daca687..e70179ea 100644 --- a/app/src/main/cpp/skyline/gpu/texture.cpp +++ b/app/src/main/cpp/skyline/gpu/texture.cpp @@ -1,24 +1,23 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include #include -#include #include -#include #include "texture.h" namespace skyline::gpu { GuestTexture::GuestTexture(const DeviceState &state, u8 *pointer, texture::Dimensions dimensions, texture::Format format, texture::TileMode tiling, texture::TileConfig layout) : state(state), pointer(pointer), dimensions(dimensions), format(format), tileMode(tiling), tileConfig(layout) {} - std::shared_ptr GuestTexture::InitializeTexture(std::optional pFormat, std::optional pDimensions, texture::Swizzle swizzle) { + std::shared_ptr GuestTexture::InitializeTexture(vk::raii::Image &&backing, std::optional pFormat, std::optional pDimensions, texture::Swizzle swizzle) { if (!host.expired()) throw exception("Trying to create multiple Texture objects from a single GuestTexture"); - auto sharedHost{std::make_shared(state, shared_from_this(), pDimensions ? *pDimensions : dimensions, pFormat ? *pFormat : format, swizzle)}; + auto sharedHost{std::make_shared(std::move(backing), shared_from_this(), pDimensions ? *pDimensions : dimensions, pFormat ? *pFormat : format, swizzle)}; host = sharedHost; return sharedHost; } - Texture::Texture(const DeviceState &state, std::shared_ptr guest, texture::Dimensions dimensions, texture::Format format, texture::Swizzle swizzle) : state(state), guest(std::move(guest)), dimensions(dimensions), format(format), swizzle(swizzle) { + Texture::Texture(vk::raii::Image&& backing, std::shared_ptr guest, texture::Dimensions dimensions, texture::Format format, texture::Swizzle swizzle) : backing(std::move(backing)), guest(std::move(guest)), dimensions(dimensions), format(format), swizzle(swizzle) { SynchronizeHost(); } @@ -26,8 +25,8 @@ namespace skyline::gpu { TRACE_EVENT("gpu", "Texture::SynchronizeHost"); auto pointer{guest->pointer}; auto size{format.GetSize(dimensions)}; - backing.resize(size); - auto output{backing.data()}; + u8* output{nullptr}; + return; if (guest->tileMode == texture::TileMode::Block) { // Reference on Block-linear tiling: https://gist.github.com/PixelyIon/d9c35050af0ef5690566ca9f0965bc32 diff --git a/app/src/main/cpp/skyline/gpu/texture.h b/app/src/main/cpp/skyline/gpu/texture.h index 5bc1dda3..af2d2ae0 100644 --- a/app/src/main/cpp/skyline/gpu/texture.h +++ b/app/src/main/cpp/skyline/gpu/texture.h @@ -6,191 +6,208 @@ #include #include -namespace skyline { - namespace service::hosbinder { - class GraphicBufferProducer; - } - namespace gpu { - namespace texture { - struct Dimensions { - u32 width; - u32 height; - u32 depth; +namespace skyline::gpu { + namespace texture { + struct Dimensions { + u32 width; + u32 height; + u32 depth; - constexpr Dimensions() : width(0), height(0), depth(0) {} + constexpr Dimensions() : width(0), height(0), depth(0) {} - constexpr Dimensions(u32 width, u32 height) : width(width), height(height), depth(1) {} + constexpr Dimensions(u32 width, u32 height) : width(width), height(height), depth(1) {} - constexpr Dimensions(u32 width, u32 height, u32 depth) : width(width), height(height), depth(depth) {} + constexpr Dimensions(u32 width, u32 height, u32 depth) : width(width), height(height), depth(depth) {} - auto operator<=>(const Dimensions &) const = default; - }; + auto operator<=>(const Dimensions &) const = default; - /** - * @note Blocks refers to the atomic unit of a compressed format (IE: The minimum amount of data that can be decompressed) - */ - struct Format { - 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::ImageType GetType() { + if (depth) + return vk::ImageType::e3D; + else if (width) + return vk::ImageType::e2D; + else + return vk::ImageType::e1D; + } - constexpr bool IsCompressed() { - return (blockHeight != 1) || (blockWidth != 1); - } - - /** - * @param width The width of the texture in pixels - * @param height The height of the texture in pixels - * @param depth The depth of the texture in layers - * @return The size of the texture in bytes - */ - constexpr size_t GetSize(u32 width, u32 height, u32 depth = 1) { - return (((width / blockWidth) * (height / blockHeight)) * bpb) * depth; - } - - constexpr size_t GetSize(Dimensions dimensions) { - return GetSize(dimensions.width, dimensions.height, dimensions.depth); - } - - constexpr bool operator==(const Format &format) { - return vkFormat == format.vkFormat; - } - - constexpr bool operator!=(const Format &format) { - return vkFormat != format.vkFormat; - } - - /** - * @return If this format is actually valid or not - */ - constexpr operator bool() { - return bpb; - } - }; - - /** - * @brief The layout of a texture in GPU memory - * @note Refer to Chapter 20.1 of the Tegra X1 TRM for information - */ - enum class TileMode { - Linear, //!< This is a purely linear texture - Pitch, //!< This is a pitch-linear texture - Block, //!< This is a 16Bx2 block-linear texture - }; - - /** - * @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 + operator vk::Extent2D() { + return vk::Extent2D{ + .width = width, + .height = height, }; - u32 pitch; //!< The pitch of the texture if it's pitch linear - }; + } - enum class SwizzleChannel { - Zero, //!< Write 0 to the channel - One, //!< Write 1 to the channel - Red, //!< Red color channel - Green, //!< Green color channel - Blue, //!< Blue color channel - Alpha, //!< Alpha channel - }; - - struct Swizzle { - SwizzleChannel red{SwizzleChannel::Red}; //!< Swizzle for the red channel - SwizzleChannel green{SwizzleChannel::Green}; //!< Swizzle for the green channel - SwizzleChannel blue{SwizzleChannel::Blue}; //!< Swizzle for the blue channel - SwizzleChannel alpha{SwizzleChannel::Alpha}; //!< Swizzle for the alpha channel - }; - } - - class Texture; + operator vk::Extent3D() { + return vk::Extent3D{ + .width = width, + .height = height, + .depth = depth, + }; + } + }; /** - * @brief A texture present in guest memory, it can be used to create a corresponding Texture object for usage on the host + * @note Blocks refers to the atomic unit of a compressed format (IE: The minimum amount of data that can be decompressed) */ - class GuestTexture : public std::enable_shared_from_this { - private: - const DeviceState &state; + struct Format { + 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; - public: - u8 *pointer; //!< The address of the texture in guest memory - std::weak_ptr host; //!< A host texture (if any) that was created from this guest texture - texture::Dimensions dimensions; - texture::Format format; - texture::TileMode tileMode; - texture::TileConfig tileConfig; - - GuestTexture(const DeviceState &state, u8 *pointer, texture::Dimensions dimensions, texture::Format format, texture::TileMode tileMode = texture::TileMode::Linear, texture::TileConfig tileConfig = {}); - - constexpr size_t Size() { - return format.GetSize(dimensions); + constexpr bool IsCompressed() { + return (blockHeight != 1) || (blockWidth != 1); } /** - * @brief Creates a corresponding host texture object for this guest texture - * @param format The format of the host texture (Defaults to the format of the guest texture) - * @param dimensions The dimensions of the host texture (Defaults to the dimensions of the host texture) - * @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 + * @param width The width of the texture in pixels + * @param height The height of the texture in pixels + * @param depth The depth of the texture in layers + * @return The size of the texture in bytes */ - std::shared_ptr InitializeTexture(std::optional format = std::nullopt, std::optional dimensions = std::nullopt, texture::Swizzle swizzle = {}); + constexpr size_t GetSize(u32 width, u32 height, u32 depth = 1) { + return (((width / blockWidth) * (height / blockHeight)) * bpb) * depth; + } + + constexpr size_t GetSize(Dimensions dimensions) { + return GetSize(dimensions.width, dimensions.height, dimensions.depth); + } + + constexpr bool operator==(const Format &format) { + return vkFormat == format.vkFormat; + } + + constexpr bool operator!=(const Format &format) { + return vkFormat != format.vkFormat; + } + + /** + * @return If this format is actually valid or not + */ + constexpr operator bool() { + return bpb; + } }; /** - * @brief A texture which is backed by host constructs while being synchronized with the underlying guest texture + * @brief The layout of a texture in GPU memory + * @note Refer to Chapter 20.1 of the Tegra X1 TRM for information */ - class Texture { - private: - const DeviceState &state; + enum class TileMode { + Linear, //!< This is a purely linear texture + Pitch, //!< This is a pitch-linear texture + Block, //!< This is a 16Bx2 block-linear texture + }; - public: - std::vector backing; //!< The object that holds a host copy of the guest texture (Will be replaced with a vk::Image) - std::shared_ptr guest; //!< The guest texture from which this was created, it's required for syncing - texture::Dimensions dimensions; - texture::Format format; - texture::Swizzle swizzle; + /** + * @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 + }; + u32 pitch; //!< The pitch of the texture if it's pitch linear + }; - public: - Texture(const DeviceState &state, std::shared_ptr guest, texture::Dimensions dimensions, texture::Format format, texture::Swizzle swizzle); + enum class SwizzleChannel { + Zero, //!< Write 0 to the channel + One, //!< Write 1 to the channel + Red, //!< Red color channel + Green, //!< Green color channel + Blue, //!< Blue color channel + Alpha, //!< Alpha channel + }; - public: - /** - * @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 - */ - void SynchronizeHost(); - - /** - * @brief Synchronizes the guest texture with the host texture after it has been modified - */ - void SynchronizeGuest(); + struct Swizzle { + SwizzleChannel red{SwizzleChannel::Red}; //!< Swizzle for the red channel + SwizzleChannel green{SwizzleChannel::Green}; //!< Swizzle for the green channel + SwizzleChannel blue{SwizzleChannel::Blue}; //!< Swizzle for the blue channel + SwizzleChannel alpha{SwizzleChannel::Alpha}; //!< Swizzle for the alpha channel }; } + + 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 + */ + class GuestTexture : public std::enable_shared_from_this { + private: + const DeviceState &state; + + public: + u8 *pointer; //!< The address of the texture in guest memory + std::weak_ptr host; //!< A host texture (if any) that was created from this guest texture + texture::Dimensions dimensions; + texture::Format format; + texture::TileMode tileMode; + texture::TileConfig tileConfig; + + GuestTexture(const DeviceState &state, u8 *pointer, texture::Dimensions dimensions, texture::Format format, texture::TileMode tileMode = texture::TileMode::Linear, texture::TileConfig tileConfig = {}); + + constexpr size_t Size() { + return format.GetSize(dimensions); + } + + /** + * @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 + * @param format The format of the host texture (Defaults to the format of the guest texture) + * @param dimensions The dimensions of the host texture (Defaults to the dimensions of the host texture) + * @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 + */ + std::shared_ptr InitializeTexture(vk::raii::Image &&backing, std::optional format = std::nullopt, std::optional dimensions = std::nullopt, texture::Swizzle swizzle = {}); + }; + + /** + * @brief A texture which is backed by host constructs while being synchronized with the underlying guest texture + */ + class Texture { + public: + vk::raii::Image backing; //!< The object that holds a host copy of the guest texture + std::shared_ptr guest; //!< The guest texture from which this was created, it's required for syncing + texture::Dimensions dimensions; + texture::Format format; + texture::Swizzle swizzle; + + public: + Texture(vk::raii::Image &&backing, std::shared_ptr guest, texture::Dimensions dimensions, texture::Format format, texture::Swizzle swizzle); + + /** + * @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 + */ + void SynchronizeHost(); + + /** + * @brief Synchronizes the guest texture with the host texture after it has been modified + */ + void SynchronizeGuest(); + }; } diff --git a/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.cpp b/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.cpp index 581dc482..ee1e18b5 100644 --- a/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.cpp +++ b/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -22,7 +23,7 @@ namespace skyline::service::hosbinder { out.Push(0); out.Push(queue.at(slot)->gbpBuffer); - state.logger->Debug("Slot: {}", slot, sizeof(GbpBuffer)); + state.logger->Debug("#{}", slot, sizeof(GbpBuffer)); } void GraphicBufferProducer::DequeueBuffer(Parcel &in, Parcel &out) { @@ -32,21 +33,16 @@ namespace skyline::service::hosbinder { u32 format{in.Pop()}; u32 usage{in.Pop()}; - std::optional slot{std::nullopt}; - while (!slot) { - for (auto &buffer : queue) { - if (buffer.second->status == BufferStatus::Free && (format == 0 || buffer.second->gbpBuffer.format == format) && buffer.second->gbpBuffer.width == width && buffer.second->gbpBuffer.height == height && (buffer.second->gbpBuffer.usage & usage) == usage) { - slot = buffer.first; - buffer.second->status = BufferStatus::Dequeued; - break; - } - } + u32 slot{state.gpu->presentation.GetFreeTexture()}; + auto &buffer{queue.at(slot)}; + if ((format != 0 && buffer->gbpBuffer.format != format) || buffer->gbpBuffer.width != width || buffer->gbpBuffer.height != height || (buffer->gbpBuffer.usage & usage) != usage) { + throw exception("Buffer which has been dequeued isn't compatible with the supplied parameters: {}x{}={}x{} F{}={} U{}={}", width, height, buffer->gbpBuffer.width, buffer->gbpBuffer.height, format, buffer->gbpBuffer.format, usage, buffer->gbpBuffer.usage); } - out.Push(*slot); + out.Push(slot); out.Push(std::array{1, 0x24}); // Unknown - state.logger->Debug("Width: {}, Height: {}, Format: {}, Usage: {}, Slot: {}", width, height, format, usage, *slot); + state.logger->Debug("#{} - Dimensions: {}x{}, Format: {}, Usage: 0x{:X}", slot, width, height, format, usage); } void GraphicBufferProducer::QueueBuffer(Parcel &in, Parcel &out) { @@ -61,14 +57,11 @@ namespace skyline::service::hosbinder { u64 _unk0_; u32 swapInterval; std::array fence; - } &data = in.Pop(); + } &data{in.Pop()}; auto buffer{queue.at(data.slot)}; - buffer->status = BufferStatus::Queued; - buffer->texture->SynchronizeHost(); state.gpu->presentation.Present(buffer->texture); - queue.at(data.slot)->status = BufferStatus::Free; state.gpu->presentation.bufferEvent->Signal(); struct { @@ -81,16 +74,17 @@ namespace skyline::service::hosbinder { }; out.Push(output); - state.logger->Debug("Timestamp: {}, Auto Timestamp: {}, Crop: [T: {}, B: {}, L: {}, R: {}], Scaling Mode: {}, Transform: {}, Sticky Transform: {}, Swap Interval: {}, Slot: {}", data.timestamp, data.autoTimestamp, data.crop.top, data.crop.bottom, data.crop.left, data.crop.right, data.scalingMode, data.transform, data.stickyTransform, data.swapInterval, data.slot); + state.logger->Debug("#{} - {}Timestamp: {}, Crop: ({}-{})x({}-{}), Scale Mode: {}, Transform: {} [Sticky: {}], Swap Interval: {}", data.slot, data.autoTimestamp ? "Auto " : "", data.timestamp, data.crop.top, data.crop.bottom, data.crop.left, data.crop.right, data.scalingMode, data.transform, data.stickyTransform, data.swapInterval); } void GraphicBufferProducer::CancelBuffer(Parcel &in) { u32 slot{in.Pop()}; //auto fences{in.Pop>()}; - queue.at(slot)->status = BufferStatus::Free; + // We cannot force the host GPU API to give us back a particular buffer due to how the swapchain works + // As a result of this, we just assume it'll be presented and dequeued at some point and not cancel the buffer here - state.logger->Debug("Slot: {}", slot); + state.logger->Debug("#{}", slot); } void GraphicBufferProducer::Connect(Parcel &out) { @@ -102,7 +96,7 @@ namespace skyline::service::hosbinder { u32 status{}; //!< The status of the buffer queue } data{}; out.Push(data); - state.logger->Debug("Connect"); + state.logger->Debug("{}x{}", data.width, data.height); } void GraphicBufferProducer::SetPreallocatedBuffer(Parcel &in) { @@ -149,10 +143,11 @@ namespace skyline::service::hosbinder { auto texture{std::make_shared(state, nvBuffer->ptr + gbpBuffer.offset, gpu::texture::Dimensions(gbpBuffer.width, gbpBuffer.height), format, gpu::texture::TileMode::Block, gpu::texture::TileConfig{.surfaceWidth = static_cast(gbpBuffer.stride), .blockHeight = static_cast(1U << gbpBuffer.blockHeightLog2), .blockDepth = 1})}; - queue[data.slot] = std::make_shared(gbpBuffer, texture->InitializeTexture()); + queue.resize(std::max(data.slot + 1, static_cast(queue.size()))); + queue[data.slot] = std::make_shared(gbpBuffer, state.gpu->presentation.CreatePresentationTexture(texture, data.slot)); state.gpu->presentation.bufferEvent->Signal(); - state.logger->Debug("Slot: {}, Magic: 0x{:X}, Width: {}, Height: {}, Stride: {}, Format: {}, Usage: {}, Index: {}, ID: {}, Handle: {}, Offset: 0x{:X}, Block Height: {}, Size: 0x{:X}", data.slot, gbpBuffer.magic, gbpBuffer.width, gbpBuffer.height, gbpBuffer.stride, gbpBuffer.format, gbpBuffer.usage, gbpBuffer.index, gbpBuffer.nvmapId, gbpBuffer.nvmapHandle, gbpBuffer.offset, (1U << gbpBuffer.blockHeightLog2), gbpBuffer.size); + state.logger->Debug("#{} - Dimensions: {}x{} [Stride: {}], Format: {}, Block Height: {}, Usage: 0x{:X}, Index: {}, NvMap: {}: {}, Buffer Start/End: 0x{:X} -> 0x{:X}", data.slot, gbpBuffer.width, gbpBuffer.stride, gbpBuffer.height, gbpBuffer.format, (1U << gbpBuffer.blockHeightLog2), gbpBuffer.usage, gbpBuffer.index, gbpBuffer.nvmapHandle ? "Handle" : "ID", gbpBuffer.nvmapHandle ? gbpBuffer.nvmapHandle : gbpBuffer.nvmapId, gbpBuffer.offset, gbpBuffer.offset + gbpBuffer.size); } void GraphicBufferProducer::OnTransact(TransactionCode code, Parcel &in, Parcel &out) { diff --git a/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.h b/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.h index d6355f7b..ef6436f7 100644 --- a/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.h +++ b/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.h @@ -35,18 +35,11 @@ namespace skyline::service::hosbinder { u32 _pad5_[58]; }; - enum class BufferStatus { - Free, //!< The buffer is free - Dequeued, //!< The buffer has been dequeued from the display - Queued, //!< The buffer is queued to be displayed - }; - /** * @brief A wrapper over GbpBuffer which contains additional state that we track for a buffer */ class Buffer { public: - BufferStatus status{BufferStatus::Free}; std::shared_ptr texture; GbpBuffer gbpBuffer; @@ -78,7 +71,7 @@ namespace skyline::service::hosbinder { class GraphicBufferProducer { private: const DeviceState &state; - std::unordered_map> queue; //!< A vector of shared pointers to all the queued buffers + std::vector> queue; //!< A vector of shared pointers to all the queued buffers /** * @brief Request for the GbpBuffer of a buffer