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
parent 305b7a6fa5
commit 25b60f2572
7 changed files with 317 additions and 248 deletions

View File

@ -40,10 +40,15 @@ namespace skyline::gpu {
}
#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
constexpr std::array<const char *, 1> requiredInstanceExtensions{
VK_EXT_DEBUG_REPORT_EXTENSION_NAME
constexpr std::array<const char *, 3> 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) {}
}

View File

@ -2,6 +2,7 @@
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <android/native_window_jni.h>
#include <gpu.h>
#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<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()};
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<u32>(ANativeWindow_getWidth(window));
resolution.height = static_cast<u32>(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> &texture) {
std::unique_lock lock(windowMutex);
if (!window)
windowConditional.wait(lock, [this]() { return window; });
std::shared_ptr<Texture> PresentationEngine::CreatePresentationTexture(const std::shared_ptr<GuestTexture> &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<u64>::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> &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<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));

View File

@ -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<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
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<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
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> &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
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <gpu.h>
#include <common/trace.h>
#include <android/native_window.h>
#include <kernel/types/KProcess.h>
#include <unistd.h>
#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<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())
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;
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();
}
@ -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

View File

@ -6,191 +6,208 @@
#include <common.h>
#include <vulkan/vulkan_raii.hpp>
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<GuestTexture> {
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<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);
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<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 {
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<u8> backing; //!< The object that holds a host copy of the guest texture (Will be replaced with a vk::Image)
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;
/**
* @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<GuestTexture> 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<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 <gpu.h>
#include <gpu/format.h>
#include <common/settings.h>
#include <services/nvdrv/driver.h>
#include <services/nvdrv/devices/nvmap.h>
#include <services/common/fence.h>
@ -22,7 +23,7 @@ namespace skyline::service::hosbinder {
out.Push<u32>(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>()};
u32 usage{in.Pop<u32>()};
std::optional<u32> 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<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) {
@ -61,14 +57,11 @@ namespace skyline::service::hosbinder {
u64 _unk0_;
u32 swapInterval;
std::array<nvdrv::Fence, 4> fence;
} &data = in.Pop<Data>();
} &data{in.Pop<Data>()};
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<u32>()};
//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) {
@ -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<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.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) {

View File

@ -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<gpu::Texture> texture;
GbpBuffer gbpBuffer;
@ -78,7 +71,7 @@ namespace skyline::service::hosbinder {
class GraphicBufferProducer {
private:
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