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.
This commit is contained in:
PixelyIon 2021-04-04 16:39:32 +05:30 committed by ◱ Mark
parent 9a09a4e815
commit 795472004e
7 changed files with 317 additions and 248 deletions

View File

@ -40,10 +40,15 @@ namespace skyline::gpu {
} }
#ifdef NDEBUG #ifdef NDEBUG
constexpr std::array<const char*, 0> requiredInstanceExtensions{}; constexpr std::array<const char*, 2> requiredInstanceExtensions{
VK_KHR_SURFACE_EXTENSION_NAME,
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
};
#else #else
constexpr std::array<const char *, 1> requiredInstanceExtensions{ constexpr std::array<const char *, 3> requiredInstanceExtensions{
VK_EXT_DEBUG_REPORT_EXTENSION_NAME VK_EXT_DEBUG_REPORT_EXTENSION_NAME,
VK_KHR_SURFACE_EXTENSION_NAME,
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
}; };
#endif #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 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 properties{physicalDevice.getProperties()}; // We should check for required properties here, if/when we have them
// auto features{physicalDevice.getFeatures()}; // Same as above // 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"); throw exception("Cannot find a queue family with both eGraphics and eCompute bits set");
}()}; }()};
if (state.logger->configLevel >= Logger::LogLevel::Debug) {
if (state.logger->configLevel >= Logger::LogLevel::Error) {
std::string extensionString; std::string extensionString;
for (const auto &extension : deviceExtensions) 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)); 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) 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 ? " <--" : ""); 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{ 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) {}
} }

View File

@ -2,6 +2,7 @@
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <android/native_window_jni.h> #include <android/native_window_jni.h>
#include <gpu.h>
#include "jvm.h" #include "jvm.h"
#include "presentation_engine.h" #include "presentation_engine.h"
@ -9,22 +10,57 @@ extern skyline::i32 Fps;
extern skyline::i32 FrameTime; extern skyline::i32 FrameTime;
namespace skyline::gpu { namespace skyline::gpu {
PresentationEngine::PresentationEngine(const DeviceState &state) : state(state), vsyncEvent(std::make_shared<kernel::type::KEvent>(state, true)), bufferEvent(std::make_shared<kernel::type::KEvent>(state, true)), presentationTrack(static_cast<u64>(trace::TrackIds::Presentation), perfetto::ProcessTrack::Current()) { PresentationEngine::PresentationEngine(const DeviceState &state, const GPU &gpu) : state(state), gpu(gpu), vsyncEvent(std::make_shared<kernel::type::KEvent>(state, true)), bufferEvent(std::make_shared<kernel::type::KEvent>(state, true)), presentationTrack(static_cast<u64>(trace::TrackIds::Presentation), perfetto::ProcessTrack::Current()) {
auto desc{presentationTrack.Serialize()}; auto desc{presentationTrack.Serialize()};
desc.set_name("Presentation"); desc.set_name("Presentation");
perfetto::TrackEvent::SetTrackDescriptor(presentationTrack, desc); perfetto::TrackEvent::SetTrackDescriptor(presentationTrack, desc);
} }
PresentationEngine::~PresentationEngine() { PresentationEngine::~PresentationEngine() {
if (window)
ANativeWindow_release(window);
auto env{state.jvm->GetEnv()}; auto env{state.jvm->GetEnv()};
if (!env->IsSameObject(surface, nullptr)) if (!env->IsSameObject(surface, nullptr))
env->DeleteGlobalRef(surface); 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) { void PresentationEngine::UpdateSurface(jobject newSurface) {
std::lock_guard lock(windowMutex); std::lock_guard lock(mutex);
auto env{state.jvm->GetEnv()}; auto env{state.jvm->GetEnv()};
if (!env->IsSameObject(surface, nullptr)) { if (!env->IsSameObject(surface, nullptr)) {
env->DeleteGlobalRef(surface); env->DeleteGlobalRef(surface);
@ -32,51 +68,47 @@ namespace skyline::gpu {
} }
if (!env->IsSameObject(newSurface, nullptr)) if (!env->IsSameObject(newSurface, nullptr))
surface = env->NewGlobalRef(newSurface); surface = env->NewGlobalRef(newSurface);
if (surface) { if (surface) {
window = ANativeWindow_fromSurface(env, surface); vkSurface.emplace(gpu.vkInstance, vk::AndroidSurfaceCreateInfoKHR{
ANativeWindow_acquire(window); .window = ANativeWindow_fromSurface(env, surface),
resolution.width = static_cast<u32>(ANativeWindow_getWidth(window)); });
resolution.height = static_cast<u32>(ANativeWindow_getHeight(window)); if (!gpu.vkPhysicalDevice.getSurfaceSupportKHR(gpu.vkQueueFamilyIndex, **vkSurface))
format = ANativeWindow_getFormat(window); throw exception("Vulkan Queue doesn't support presentation with surface");
windowConditional.notify_all();
UpdateSwapchain(swapchain.imageCount, swapchain.imageFormat, swapchain.imageExtent);
surfaceCondition.notify_all();
} else { } else {
window = nullptr; vkSurface.reset();
} }
} }
void PresentationEngine::Present(const std::shared_ptr<Texture> &texture) { std::shared_ptr<Texture> PresentationEngine::CreatePresentationTexture(const std::shared_ptr<GuestTexture> &texture, u32 slot) {
std::unique_lock lock(windowMutex); std::unique_lock lock(mutex);
if (!window) if (swapchain.imageCount <= slot)
windowConditional.wait(lock, [this]() { return window; }); UpdateSwapchain(slot + 1, texture->format.vkFormat, texture->dimensions);
return texture->InitializeTexture(vk::raii::Image(gpu.vkDevice, vkSwapchain->getImages().at(slot)));
}
auto textureFormat{[&texture]() { u32 PresentationEngine::GetFreeTexture() {
switch (texture->format.vkFormat) { std::unique_lock lock(mutex);
case vk::Format::eR8G8B8A8Unorm: auto nextImage{vkSwapchain->acquireNextImage(std::numeric_limits<u64>::max())};
return WINDOW_FORMAT_RGBA_8888; if (nextImage.first == vk::Result::eErrorSurfaceLostKHR || nextImage.first == vk::Result::eSuboptimalKHR) {
case vk::Format::eR5G6B5UnormPack16: surfaceCondition.wait(lock, [this]() { return vkSurface.has_value(); });
return WINDOW_FORMAT_RGB_565; return GetFreeTexture();
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;
} }
return nextImage.second;
}
ANativeWindow_Buffer buffer; void PresentationEngine::Present(const std::shared_ptr<Texture> &texture) {
ARect rect; std::unique_lock lock(mutex);
surfaceCondition.wait(lock, [this]() { return vkSurface.has_value(); });
ANativeWindow_lock(window, &buffer, &rect);
std::memcpy(buffer.bits, texture->backing.data(), texture->backing.size());
ANativeWindow_unlockAndPost(window);
vsyncEvent->Signal(); vsyncEvent->Signal();
if (frameTimestamp) { if (frameTimestamp) {
auto now{util::GetTimeNs()}; auto now{util::GetTimeNs()};
FrameTime = static_cast<u32>((now - frameTimestamp) / 10000); // frametime / 100 is the real ms value, this is to retain the first two decimals FrameTime = static_cast<u32>((now - frameTimestamp) / 10000); // frametime / 100 is the real ms value, this is to retain the first two decimals
Fps = static_cast<u16>(constant::NsInSecond / (now - frameTimestamp)); Fps = static_cast<u16>(constant::NsInSecond / (now - frameTimestamp));

View File

@ -10,29 +10,58 @@
struct ANativeWindow; struct ANativeWindow;
namespace skyline::gpu { 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 { class PresentationEngine {
private: private:
const DeviceState &state; const DeviceState &state;
std::mutex windowMutex; const GPU &gpu;
std::condition_variable windowConditional; 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 jobject surface{}; //!< The Surface object backing the ANativeWindow
std::optional<vk::raii::SurfaceKHR> vkSurface; //!< The Vulkan Surface object that is backed by ANativeWindow
std::optional<vk::raii::SwapchainKHR> 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 u64 frameTimestamp{}; //!< The timestamp of the last frame being shown
perfetto::Track presentationTrack; //!< Perfetto track used for presentation events perfetto::Track presentationTrack; //!< Perfetto track used for presentation events
void UpdateSwapchain(u32 imageCount, vk::Format imageFormat, vk::Extent2D imageExtent);
public: public:
texture::Dimensions resolution{}; texture::Dimensions resolution{};
i32 format{}; i32 format{};
std::shared_ptr<kernel::type::KEvent> vsyncEvent; //!< Signalled every time a frame is drawn std::shared_ptr<kernel::type::KEvent> vsyncEvent; //!< Signalled every time a frame is drawn
std::shared_ptr<kernel::type::KEvent> bufferEvent; //!< Signalled every time a buffer is freed std::shared_ptr<kernel::type::KEvent> bufferEvent; //!< Signalled every time a buffer is freed
PresentationEngine(const DeviceState &state); PresentationEngine(const DeviceState &state, const GPU& gpu);
~PresentationEngine(); ~PresentationEngine();
/**
* @brief Replaces the underlying Android surface with a new one, it handles resetting the swapchain and such
*/
void UpdateSurface(jobject newSurface); void UpdateSurface(jobject newSurface);
void Present(const std::shared_ptr<Texture> &texture); /**
* @brief Creates a Texture object from a GuestTexture as a part of the Vulkan swapchain
*/
std::shared_ptr<Texture> CreatePresentationTexture(const std::shared_ptr<GuestTexture> &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> &texture);
}; };
} }

View File

@ -1,24 +1,23 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <gpu.h>
#include <common/trace.h> #include <common/trace.h>
#include <android/native_window.h>
#include <kernel/types/KProcess.h> #include <kernel/types/KProcess.h>
#include <unistd.h>
#include "texture.h" #include "texture.h"
namespace skyline::gpu { 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) {} 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<Texture> GuestTexture::InitializeTexture(std::optional<texture::Format> pFormat, std::optional<texture::Dimensions> pDimensions, texture::Swizzle swizzle) { std::shared_ptr<Texture> GuestTexture::InitializeTexture(vk::raii::Image &&backing, std::optional<texture::Format> pFormat, std::optional<texture::Dimensions> pDimensions, texture::Swizzle swizzle) {
if (!host.expired()) if (!host.expired())
throw exception("Trying to create multiple Texture objects from a single GuestTexture"); throw exception("Trying to create multiple Texture objects from a single GuestTexture");
auto sharedHost{std::make_shared<Texture>(state, shared_from_this(), pDimensions ? *pDimensions : dimensions, pFormat ? *pFormat : format, swizzle)}; auto sharedHost{std::make_shared<Texture>(std::move(backing), shared_from_this(), pDimensions ? *pDimensions : dimensions, pFormat ? *pFormat : format, swizzle)};
host = sharedHost; host = sharedHost;
return sharedHost; return sharedHost;
} }
Texture::Texture(const DeviceState &state, std::shared_ptr<GuestTexture> 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<GuestTexture> 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(); SynchronizeHost();
} }
@ -26,8 +25,8 @@ namespace skyline::gpu {
TRACE_EVENT("gpu", "Texture::SynchronizeHost"); TRACE_EVENT("gpu", "Texture::SynchronizeHost");
auto pointer{guest->pointer}; auto pointer{guest->pointer};
auto size{format.GetSize(dimensions)}; auto size{format.GetSize(dimensions)};
backing.resize(size); u8* output{nullptr};
auto output{backing.data()}; return;
if (guest->tileMode == texture::TileMode::Block) { if (guest->tileMode == texture::TileMode::Block) {
// Reference on Block-linear tiling: https://gist.github.com/PixelyIon/d9c35050af0ef5690566ca9f0965bc32 // Reference on Block-linear tiling: https://gist.github.com/PixelyIon/d9c35050af0ef5690566ca9f0965bc32

View File

@ -6,191 +6,208 @@
#include <common.h> #include <common.h>
#include <vulkan/vulkan_raii.hpp> #include <vulkan/vulkan_raii.hpp>
namespace skyline { namespace skyline::gpu {
namespace service::hosbinder { namespace texture {
class GraphicBufferProducer; struct Dimensions {
} u32 width;
namespace gpu { u32 height;
namespace texture { u32 depth;
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;
};
/** vk::ImageType GetType() {
* @note Blocks refers to the atomic unit of a compressed format (IE: The minimum amount of data that can be decompressed) if (depth)
*/ return vk::ImageType::e3D;
struct Format { else if (width)
u8 bpb; //!< Bytes Per Block, this is used instead of bytes per pixel as that might not be a whole number for compressed formats return vk::ImageType::e2D;
u16 blockHeight; //!< The height of a block in pixels else
u16 blockWidth; //!< The width of a block in pixels return vk::ImageType::e1D;
vk::Format vkFormat; }
constexpr bool IsCompressed() { operator vk::Extent2D() {
return (blockHeight != 1) || (blockWidth != 1); return vk::Extent2D{
} .width = width,
.height = height,
/**
* @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
}; };
u32 pitch; //!< The pitch of the texture if it's pitch linear }
};
enum class SwizzleChannel { operator vk::Extent3D() {
Zero, //!< Write 0 to the channel return vk::Extent3D{
One, //!< Write 1 to the channel .width = width,
Red, //!< Red color channel .height = height,
Green, //!< Green color channel .depth = depth,
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;
/** /**
* @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<GuestTexture> { struct Format {
private: u8 bpb; //!< Bytes Per Block, this is used instead of bytes per pixel as that might not be a whole number for compressed formats
const DeviceState &state; u16 blockHeight; //!< The height of a block in pixels
u16 blockWidth; //!< The width of a block in pixels
vk::Format vkFormat;
public: constexpr bool IsCompressed() {
u8 *pointer; //!< The address of the texture in guest memory return (blockHeight != 1) || (blockWidth != 1);
std::weak_ptr<Texture> 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 width The width of the texture in pixels
* @param format The format of the host texture (Defaults to the format of the guest texture) * @param height The height of the texture in pixels
* @param dimensions The dimensions of the host texture (Defaults to the dimensions of the host texture) * @param depth The depth of the texture in layers
* @param swizzle The channel swizzle of the host texture (Defaults to no channel swizzling) * @return The size of the texture in bytes
* @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<Texture> InitializeTexture(std::optional<texture::Format> format = std::nullopt, std::optional<texture::Dimensions> 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 { enum class TileMode {
private: Linear, //!< This is a purely linear texture
const DeviceState &state; Pitch, //!< This is a pitch-linear texture
Block, //!< This is a 16Bx2 block-linear texture
};
public: /**
std::vector<u8> backing; //!< The object that holds a host copy of the guest texture (Will be replaced with a vk::Image) * @brief The parameters of the tiling mode, covered in Table 76 in the Tegra X1 TRM
std::shared_ptr<GuestTexture> guest; //!< The guest texture from which this was created, it's required for syncing */
texture::Dimensions dimensions; union TileConfig {
texture::Format format; struct {
texture::Swizzle swizzle; 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: enum class SwizzleChannel {
Texture(const DeviceState &state, std::shared_ptr<GuestTexture> guest, texture::Dimensions dimensions, texture::Format format, texture::Swizzle swizzle); 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: struct Swizzle {
/** SwizzleChannel red{SwizzleChannel::Red}; //!< Swizzle for the red channel
* @brief Convert this texture to the specified tiling mode SwizzleChannel green{SwizzleChannel::Green}; //!< Swizzle for the green channel
* @param tileMode The tiling mode to convert it to SwizzleChannel blue{SwizzleChannel::Blue}; //!< Swizzle for the blue channel
* @param tileConfig The configuration for the tiling mode (Can be default argument for Linear) SwizzleChannel alpha{SwizzleChannel::Alpha}; //!< Swizzle for the alpha channel
*/
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();
}; };
} }
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<GuestTexture> {
private:
const DeviceState &state;
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
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<Texture> InitializeTexture(vk::raii::Image &&backing, std::optional<texture::Format> format = std::nullopt, std::optional<texture::Dimensions> 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<GuestTexture> 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<GuestTexture> 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();
};
} }

View File

@ -4,6 +4,7 @@
#include <android/hardware_buffer.h> #include <android/hardware_buffer.h>
#include <gpu.h> #include <gpu.h>
#include <gpu/format.h> #include <gpu/format.h>
#include <common/settings.h>
#include <services/nvdrv/driver.h> #include <services/nvdrv/driver.h>
#include <services/nvdrv/devices/nvmap.h> #include <services/nvdrv/devices/nvmap.h>
#include <services/common/fence.h> #include <services/common/fence.h>
@ -22,7 +23,7 @@ namespace skyline::service::hosbinder {
out.Push<u32>(0); out.Push<u32>(0);
out.Push(queue.at(slot)->gbpBuffer); 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) { void GraphicBufferProducer::DequeueBuffer(Parcel &in, Parcel &out) {
@ -32,21 +33,16 @@ namespace skyline::service::hosbinder {
u32 format{in.Pop<u32>()}; u32 format{in.Pop<u32>()};
u32 usage{in.Pop<u32>()}; u32 usage{in.Pop<u32>()};
std::optional<u32> slot{std::nullopt}; u32 slot{state.gpu->presentation.GetFreeTexture()};
while (!slot) { auto &buffer{queue.at(slot)};
for (auto &buffer : queue) { if ((format != 0 && buffer->gbpBuffer.format != format) || buffer->gbpBuffer.width != width || buffer->gbpBuffer.height != height || (buffer->gbpBuffer.usage & usage) != usage) {
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) { 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);
slot = buffer.first;
buffer.second->status = BufferStatus::Dequeued;
break;
}
}
} }
out.Push(*slot); out.Push(slot);
out.Push(std::array<u32, 13>{1, 0x24}); // Unknown out.Push(std::array<u32, 13>{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) { void GraphicBufferProducer::QueueBuffer(Parcel &in, Parcel &out) {
@ -61,14 +57,11 @@ namespace skyline::service::hosbinder {
u64 _unk0_; u64 _unk0_;
u32 swapInterval; u32 swapInterval;
std::array<nvdrv::Fence, 4> fence; std::array<nvdrv::Fence, 4> fence;
} &data = in.Pop<Data>(); } &data{in.Pop<Data>()};
auto buffer{queue.at(data.slot)}; auto buffer{queue.at(data.slot)};
buffer->status = BufferStatus::Queued;
buffer->texture->SynchronizeHost(); buffer->texture->SynchronizeHost();
state.gpu->presentation.Present(buffer->texture); state.gpu->presentation.Present(buffer->texture);
queue.at(data.slot)->status = BufferStatus::Free;
state.gpu->presentation.bufferEvent->Signal(); state.gpu->presentation.bufferEvent->Signal();
struct { struct {
@ -81,16 +74,17 @@ namespace skyline::service::hosbinder {
}; };
out.Push(output); 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) { void GraphicBufferProducer::CancelBuffer(Parcel &in) {
u32 slot{in.Pop<u32>()}; u32 slot{in.Pop<u32>()};
//auto fences{in.Pop<std::array<nvdrv::Fence, 4>>()}; //auto fences{in.Pop<std::array<nvdrv::Fence, 4>>()};
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) { void GraphicBufferProducer::Connect(Parcel &out) {
@ -102,7 +96,7 @@ namespace skyline::service::hosbinder {
u32 status{}; //!< The status of the buffer queue u32 status{}; //!< The status of the buffer queue
} data{}; } data{};
out.Push(data); out.Push(data);
state.logger->Debug("Connect"); state.logger->Debug("{}x{}", data.width, data.height);
} }
void GraphicBufferProducer::SetPreallocatedBuffer(Parcel &in) { void GraphicBufferProducer::SetPreallocatedBuffer(Parcel &in) {
@ -149,10 +143,11 @@ namespace skyline::service::hosbinder {
auto texture{std::make_shared<gpu::GuestTexture>(state, nvBuffer->ptr + gbpBuffer.offset, gpu::texture::Dimensions(gbpBuffer.width, gbpBuffer.height), format, gpu::texture::TileMode::Block, gpu::texture::TileConfig{.surfaceWidth = static_cast<u16>(gbpBuffer.stride), .blockHeight = static_cast<u8>(1U << gbpBuffer.blockHeightLog2), .blockDepth = 1})}; auto texture{std::make_shared<gpu::GuestTexture>(state, nvBuffer->ptr + gbpBuffer.offset, gpu::texture::Dimensions(gbpBuffer.width, gbpBuffer.height), format, gpu::texture::TileMode::Block, gpu::texture::TileConfig{.surfaceWidth = static_cast<u16>(gbpBuffer.stride), .blockHeight = static_cast<u8>(1U << gbpBuffer.blockHeightLog2), .blockDepth = 1})};
queue[data.slot] = std::make_shared<Buffer>(gbpBuffer, texture->InitializeTexture()); queue.resize(std::max(data.slot + 1, static_cast<u32>(queue.size())));
queue[data.slot] = std::make_shared<Buffer>(gbpBuffer, state.gpu->presentation.CreatePresentationTexture(texture, data.slot));
state.gpu->presentation.bufferEvent->Signal(); 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) { void GraphicBufferProducer::OnTransact(TransactionCode code, Parcel &in, Parcel &out) {

View File

@ -35,18 +35,11 @@ namespace skyline::service::hosbinder {
u32 _pad5_[58]; 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 * @brief A wrapper over GbpBuffer which contains additional state that we track for a buffer
*/ */
class Buffer { class Buffer {
public: public:
BufferStatus status{BufferStatus::Free};
std::shared_ptr<gpu::Texture> texture; std::shared_ptr<gpu::Texture> texture;
GbpBuffer gbpBuffer; GbpBuffer gbpBuffer;
@ -78,7 +71,7 @@ namespace skyline::service::hosbinder {
class GraphicBufferProducer { class GraphicBufferProducer {
private: private:
const DeviceState &state; const DeviceState &state;
std::unordered_map<u32, std::shared_ptr<Buffer>> queue; //!< A vector of shared pointers to all the queued buffers std::vector<std::shared_ptr<Buffer>> queue; //!< A vector of shared pointers to all the queued buffers
/** /**
* @brief Request for the GbpBuffer of a buffer * @brief Request for the GbpBuffer of a buffer