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