Rework GraphicBufferProducer

This commit makes GraphicBufferProducer significantly more accurate by matching the behavior of AOSP alongside mirroring the tweaks made by Nintendo. 

It eliminates a lot of the magic structures and enumerations used prior and replaces them with the correct values from AOSP or HOS. 

There was a lot of functional inaccuracy as well which was fixed, we emulate the exact subset of HOS behavior that we need to. A lot of the intermediate layers such as GraphicBufferConsumer or Gralloc/Sync are not emulated as they're pointless abstractions here.
This commit is contained in:
PixelyIon 2021-04-16 20:35:24 +05:30 committed by ◱ Mark
parent da7e18de4c
commit d8025e7178
21 changed files with 1035 additions and 243 deletions

View File

@ -119,11 +119,6 @@ namespace skyline {
}; };
namespace constant { namespace constant {
// Display
constexpr u16 HandheldResolutionW{1280}; //!< The width component of the handheld resolution
constexpr u16 HandheldResolutionH{720}; //!< The height component of the handheld resolution
constexpr u16 DockedResolutionW{1920}; //!< The width component of the docked resolution
constexpr u16 DockedResolutionH{1080}; //!< The height component of the docked resolution
// Time // Time
constexpr u64 NsInSecond{1000000000}; //!< The amount of nanoseconds in a second constexpr u64 NsInSecond{1000000000}; //!< The amount of nanoseconds in a second
constexpr u64 NsInDay{86400000000000UL}; //!< The amount of nanoseconds in a day constexpr u64 NsInDay{86400000000000UL}; //!< The amount of nanoseconds in a day
@ -191,7 +186,7 @@ namespace skyline {
* @brief A way to implicitly convert a pointer to uintptr_t and leave it unaffected if it isn't a pointer * @brief A way to implicitly convert a pointer to uintptr_t and leave it unaffected if it isn't a pointer
*/ */
template<typename T> template<typename T>
T PointerValue(T item) { constexpr T PointerValue(T item) {
return item; return item;
} }
@ -204,7 +199,7 @@ namespace skyline {
* @brief A way to implicitly convert an integral to a pointer, if the return type is a pointer * @brief A way to implicitly convert an integral to a pointer, if the return type is a pointer
*/ */
template<typename Return, typename T> template<typename Return, typename T>
Return ValuePointer(T item) { constexpr Return ValuePointer(T item) {
if constexpr (std::is_pointer<Return>::value) if constexpr (std::is_pointer<Return>::value)
return reinterpret_cast<Return>(item); return reinterpret_cast<Return>(item);
else else

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: LGPL-3.0-or-later // SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <jvm.h> #include <jvm.h>

View File

@ -3,7 +3,7 @@
#include <android/native_window_jni.h> #include <android/native_window_jni.h>
#include <gpu.h> #include <gpu.h>
#include "jvm.h" #include <jvm.h>
#include "presentation_engine.h" #include "presentation_engine.h"
extern skyline::i32 Fps; extern skyline::i32 Fps;
@ -22,7 +22,30 @@ namespace skyline::gpu {
env->DeleteGlobalRef(surface); env->DeleteGlobalRef(surface);
} }
void PresentationEngine::UpdateSwapchain(u32 imageCount, vk::Format imageFormat, vk::Extent2D imageExtent) { service::hosbinder::NativeWindowTransform GetAndroidTransform(vk::SurfaceTransformFlagBitsKHR transform) {
using NativeWindowTransform = service::hosbinder::NativeWindowTransform;
switch (transform) {
case vk::SurfaceTransformFlagBitsKHR::eIdentity:
case vk::SurfaceTransformFlagBitsKHR::eInherit:
return NativeWindowTransform::Identity;
case vk::SurfaceTransformFlagBitsKHR::eRotate90:
return NativeWindowTransform::Rotate90;
case vk::SurfaceTransformFlagBitsKHR::eRotate180:
return NativeWindowTransform::Rotate180;
case vk::SurfaceTransformFlagBitsKHR::eRotate270:
return NativeWindowTransform::Rotate270;
case vk::SurfaceTransformFlagBitsKHR::eHorizontalMirror:
return NativeWindowTransform::MirrorHorizontal;
case vk::SurfaceTransformFlagBitsKHR::eHorizontalMirrorRotate90:
return NativeWindowTransform::MirrorHorizontalRotate90;
case vk::SurfaceTransformFlagBitsKHR::eHorizontalMirrorRotate180:
return NativeWindowTransform::MirrorVertical;
case vk::SurfaceTransformFlagBitsKHR::eHorizontalMirrorRotate270:
return NativeWindowTransform::MirrorVerticalRotate90;
}
}
void PresentationEngine::UpdateSwapchain(u16 imageCount, vk::Format imageFormat, vk::Extent2D imageExtent) {
if (!imageCount) if (!imageCount)
return; return;
@ -36,6 +59,8 @@ namespace skyline::gpu {
if ((capabilities.supportedUsageFlags & presentUsage) != presentUsage) if ((capabilities.supportedUsageFlags & presentUsage) != presentUsage)
throw exception("Swapchain doesn't support image usage '{}': {}", vk::to_string(presentUsage), vk::to_string(capabilities.supportedUsageFlags)); throw exception("Swapchain doesn't support image usage '{}': {}", vk::to_string(presentUsage), vk::to_string(capabilities.supportedUsageFlags));
transformHint = GetAndroidTransform(capabilities.currentTransform);
vkSwapchain = vk::raii::SwapchainKHR(gpu.vkDevice, vk::SwapchainCreateInfoKHR{ vkSwapchain = vk::raii::SwapchainKHR(gpu.vkDevice, vk::SwapchainCreateInfoKHR{
.surface = **vkSurface, .surface = **vkSurface,
.minImageCount = imageCount, .minImageCount = imageCount,
@ -59,7 +84,7 @@ namespace skyline::gpu {
} }
void PresentationEngine::UpdateSurface(jobject newSurface) { void PresentationEngine::UpdateSurface(jobject newSurface) {
std::lock_guard lock(mutex); std::lock_guard guard(mutex);
auto env{state.jvm->GetEnv()}; auto env{state.jvm->GetEnv()};
if (!env->IsSameObject(surface, nullptr)) { if (!env->IsSameObject(surface, nullptr)) {
@ -85,26 +110,45 @@ namespace skyline::gpu {
} }
std::shared_ptr<Texture> PresentationEngine::CreatePresentationTexture(const std::shared_ptr<GuestTexture> &texture, u32 slot) { std::shared_ptr<Texture> PresentationEngine::CreatePresentationTexture(const std::shared_ptr<GuestTexture> &texture, u32 slot) {
std::unique_lock lock(mutex); std::lock_guard guard(mutex);
if (swapchain.imageCount <= slot) if (swapchain.imageCount <= slot)
UpdateSwapchain(slot + 1, texture->format.vkFormat, texture->dimensions); UpdateSwapchain(std::max(slot + 1, 2U), texture->format.vkFormat, texture->dimensions);
return texture->InitializeTexture(vk::raii::Image(gpu.vkDevice, vkSwapchain->getImages().at(slot))); return texture->InitializeTexture(vk::raii::Image(gpu.vkDevice, vkSwapchain->getImages().at(slot)));
} }
u32 PresentationEngine::GetFreeTexture() { service::hosbinder::AndroidStatus PresentationEngine::GetFreeTexture(bool async, i32 &slot) {
using AndroidStatus = service::hosbinder::AndroidStatus;
std::unique_lock lock(mutex); std::unique_lock lock(mutex);
auto nextImage{vkSwapchain->acquireNextImage(std::numeric_limits<u64>::max())}; if (swapchain.dequeuedCount < swapchain.imageCount) {
if (nextImage.first == vk::Result::eErrorSurfaceLostKHR || nextImage.first == vk::Result::eSuboptimalKHR) { swapchain.dequeuedCount++;
vk::raii::Fence fence(state.gpu->vkDevice, vk::FenceCreateInfo{});
auto timeout{async ? 0ULL : std::numeric_limits<u64>::max()}; // We cannot block for a buffer to be retrieved in async mode
auto nextImage{vkSwapchain->acquireNextImage(timeout, {}, *fence)};
if (nextImage.first == vk::Result::eTimeout) {
return AndroidStatus::WouldBlock;
} else if (nextImage.first == vk::Result::eErrorSurfaceLostKHR || nextImage.first == vk::Result::eSuboptimalKHR) {
surfaceCondition.wait(lock, [this]() { return vkSurface.has_value(); }); surfaceCondition.wait(lock, [this]() { return vkSurface.has_value(); });
return GetFreeTexture(); return GetFreeTexture(async, slot);
}
return nextImage.second;
} }
void PresentationEngine::Present(const std::shared_ptr<Texture> &texture) { gpu.vkDevice.waitForFences(*fence, true, std::numeric_limits<u64>::max());
slot = nextImage.second;
return AndroidStatus::Ok;
}
return AndroidStatus::Busy;
}
void PresentationEngine::Present(i32 slot) {
std::unique_lock lock(mutex); std::unique_lock lock(mutex);
surfaceCondition.wait(lock, [this]() { return vkSurface.has_value(); }); surfaceCondition.wait(lock, [this]() { return vkSurface.has_value(); });
if (--swapchain.dequeuedCount < 0) [[unlikely]] {
throw exception("Swapchain has been presented more times than images from it have been acquired: {} (Image Count: {})", swapchain.dequeuedCount, swapchain.imageCount);
}
vsyncEvent->Signal(); vsyncEvent->Signal();
if (frameTimestamp) { if (frameTimestamp) {
@ -119,4 +163,12 @@ namespace skyline::gpu {
frameTimestamp = util::GetTimeNs(); frameTimestamp = util::GetTimeNs();
} }
} }
service::hosbinder::NativeWindowTransform PresentationEngine::GetTransformHint() {
std::unique_lock lock(mutex);
surfaceCondition.wait(lock, [this]() { return vkSurface.has_value(); });
if (!transformHint)
transformHint = GetAndroidTransform(gpu.vkPhysicalDevice.getSurfaceCapabilitiesKHR(**vkSurface).currentTransform);
return *transformHint;
}
} }

View File

@ -5,6 +5,8 @@
#include <common/trace.h> #include <common/trace.h>
#include <kernel/types/KEvent.h> #include <kernel/types/KEvent.h>
#include <services/hosbinder/native_window.h>
#include <services/hosbinder/android_types.h>
#include "texture.h" #include "texture.h"
struct ANativeWindow; struct ANativeWindow;
@ -18,13 +20,15 @@ namespace skyline::gpu {
const DeviceState &state; const DeviceState &state;
const GPU &gpu; const GPU &gpu;
std::mutex mutex; //!< Synchronizes access to the surface objects 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 std::condition_variable surfaceCondition; //!< Allows us to efficiently wait for Vulkan surface to be initialized
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::SurfaceKHR> vkSurface; //!< The Vulkan Surface object that is backed by ANativeWindow
std::optional<service::hosbinder::NativeWindowTransform> transformHint; //!< The optimal transform for the application to render as
std::optional<vk::raii::SwapchainKHR> vkSwapchain; //!< The Vulkan swapchain and the properties associated with it std::optional<vk::raii::SwapchainKHR> vkSwapchain; //!< The Vulkan swapchain and the properties associated with it
struct SwapchainContext { struct SwapchainContext {
u32 imageCount{}; u16 imageCount{};
i32 dequeuedCount{};
vk::Format imageFormat{}; vk::Format imageFormat{};
vk::Extent2D imageExtent{}; vk::Extent2D imageExtent{};
} swapchain; //!< The properties of the currently created swapchain } swapchain; //!< The properties of the currently created swapchain
@ -32,7 +36,7 @@ namespace skyline::gpu {
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); void UpdateSwapchain(u16 imageCount, vk::Format imageFormat, vk::Extent2D imageExtent);
public: public:
texture::Dimensions resolution{}; texture::Dimensions resolution{};
@ -55,13 +59,19 @@ namespace skyline::gpu {
std::shared_ptr<Texture> CreatePresentationTexture(const std::shared_ptr<GuestTexture> &texture, u32 slot); std::shared_ptr<Texture> CreatePresentationTexture(const std::shared_ptr<GuestTexture> &texture, u32 slot);
/** /**
* @return The slot of the texture that's available to write into * @param async If to return immediately when a texture is not available
* @param slot The slot the freed texture is in is written into this, it is untouched if there's an error
*/ */
u32 GetFreeTexture(); service::hosbinder::AndroidStatus GetFreeTexture(bool async, i32& slot);
/** /**
* @brief Send the supplied texture to the presentation queue to be displayed * @brief Send a texture from a slot to the presentation queue to be displayed
*/ */
void Present(const std::shared_ptr<Texture> &texture); void Present(i32 slot);
/**
* @return A transform that the application should render with to elide costly transforms later
*/
service::hosbinder::NativeWindowTransform GetTransformHint();
}; };
} }

View File

@ -94,9 +94,9 @@ namespace skyline::gpu {
* @note Refer to Chapter 20.1 of the Tegra X1 TRM for information * @note Refer to Chapter 20.1 of the Tegra X1 TRM for information
*/ */
enum class TileMode { enum class TileMode {
Linear, //!< This is a purely linear texture Linear, //!< All pixels are arranged linearly
Pitch, //!< This is a pitch-linear texture Pitch, //!< All pixels are arranged linearly but rows aligned to the pitch
Block, //!< This is a 16Bx2 block-linear texture Block, //!< All pixels are arranged into blocks and swizzled in a Z-order curve to optimize for spacial locality
}; };
/** /**

View File

@ -54,11 +54,15 @@ namespace skyline::service::am {
Result ICommonStateGetter::GetDefaultDisplayResolution(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { Result ICommonStateGetter::GetDefaultDisplayResolution(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
if (operationMode == OperationMode::Handheld) { if (operationMode == OperationMode::Handheld) {
response.Push<u32>(constant::HandheldResolutionW); constexpr u16 HandheldResolutionW{1280};
response.Push<u32>(constant::HandheldResolutionH); constexpr u16 HandheldResolutionH{720};
response.Push<u32>(HandheldResolutionW);
response.Push<u32>(HandheldResolutionH);
} else if (operationMode == OperationMode::Docked) { } else if (operationMode == OperationMode::Docked) {
response.Push<u32>(constant::DockedResolutionW); constexpr u16 DockedResolutionW{1920};
response.Push<u32>(constant::DockedResolutionH); constexpr u16 DockedResolutionH{1080};
response.Push<u32>(DockedResolutionW);
response.Push<u32>(DockedResolutionH);
} }
return {}; return {};
} }

View File

@ -49,16 +49,52 @@ namespace skyline::service {
return value; return value;
} }
/**
* @return A pointer to an optional flattenable from the top of data, nullptr will be returned if the object doesn't exist
*/
template<typename ValueType>
ValueType* PopOptionalFlattenable() {
bool hasObject{static_cast<bool>(Pop<u32>())};
if (hasObject) {
auto size{Pop<u64>()};
if (size != sizeof(ValueType))
throw exception("Popping flattenable of size 0x{:X} with type size 0x{:X}", size, sizeof(ValueType));
return &Pop<ValueType>();
} else {
return nullptr;
}
}
/** /**
* @brief Writes a value to the Parcel * @brief Writes a value to the Parcel
*/ */
template<typename ValueType> template<typename ValueType>
void Push(const ValueType &value) { void Push(const ValueType &value) {
data.reserve(data.size() + sizeof(ValueType)); auto offset{data.size()};
auto item{reinterpret_cast<const u8 *>(&value)}; data.resize(offset + sizeof(ValueType));
for (size_t index{}; sizeof(ValueType) > index; index++) { *(reinterpret_cast<ValueType*>(data.data() + offset)) = value;
data.push_back(*item); }
item++;
/**
* @brief Writes a 32-bit boolean flag denoting if an object exists alongside the object if it exists
*/
template<typename ObjectType>
void PushOptionalFlattenable(ObjectType *pointer) {
Push<u32>(pointer != nullptr);
if (pointer) {
Push<u32>(sizeof(ObjectType)); // Object Size
Push<u32>(0); // FD Count
Push(*pointer);
}
}
template<typename ObjectType>
void PushOptionalFlattenable(std::optional<ObjectType> object) {
Push<u32>(object.has_value());
if (object) {
Push<u32>(sizeof(ObjectType));
Push<u32>(0);
Push(*object);
} }
} }
@ -67,12 +103,9 @@ namespace skyline::service {
*/ */
template<typename ObjectType> template<typename ObjectType>
void PushObject(const ObjectType &object) { void PushObject(const ObjectType &object) {
objects.reserve(objects.size() + sizeof(ObjectType)); auto offset{objects.size()};
auto item{reinterpret_cast<const u8 *>(&object)}; objects.resize(offset + sizeof(ObjectType));
for (size_t index{}; sizeof(ObjectType) > index; index++) { *(reinterpret_cast<ObjectType*>(objects.data() + offset)) = object;
objects.push_back(*object);
item++;
}
} }
/** /**

View File

@ -1,9 +1,12 @@
// 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/)
// Copyright © 2005 The Android Open Source Project
// Copyright © 2019-2020 Ryujinx Team and Contributors
#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 <soc.h>
#include <common/settings.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>
@ -11,170 +14,449 @@
#include "GraphicBufferProducer.h" #include "GraphicBufferProducer.h"
namespace skyline::service::hosbinder { namespace skyline::service::hosbinder {
Buffer::Buffer(const GbpBuffer &gbpBuffer, std::shared_ptr<gpu::Texture> texture) : gbpBuffer(gbpBuffer), texture(std::move(texture)) {}
GraphicBufferProducer::GraphicBufferProducer(const DeviceState &state) : state(state) {} GraphicBufferProducer::GraphicBufferProducer(const DeviceState &state) : state(state) {}
void GraphicBufferProducer::RequestBuffer(Parcel &in, Parcel &out) { u8 GraphicBufferProducer::GetPendingBufferCount() {
u32 slot{in.Pop<u32>()}; u8 count{};
for (auto it{queue.begin()}, end{it + activeSlotCount}; it < end; it++)
out.Push<u32>(1); if (it->state == BufferState::Queued)
out.Push<u32>(sizeof(GbpBuffer)); count++;
out.Push<u32>(0); return count;
out.Push(queue.at(slot)->gbpBuffer);
state.logger->Debug("#{}", slot, sizeof(GbpBuffer));
} }
void GraphicBufferProducer::DequeueBuffer(Parcel &in, Parcel &out) { AndroidStatus GraphicBufferProducer::RequestBuffer(i32 slot, GraphicBuffer *&buffer) {
in.Pop<u32>(); //!< async boolean flag std::lock_guard guard(mutex);
u32 width{in.Pop<u32>()}; if (slot < 0 || slot >= queue.size()) [[unlikely]] {
u32 height{in.Pop<u32>()}; state.logger->Warn("#{} was out of range", slot);
u32 format{in.Pop<u32>()}; return AndroidStatus::BadValue;
u32 usage{in.Pop<u32>()}; }
auto &bufferSlot{queue[slot]};
bufferSlot.wasBufferRequested = true;
buffer = bufferSlot.graphicBuffer.get();
state.logger->Debug("#{}", slot);
return AndroidStatus::Ok;
}
AndroidStatus GraphicBufferProducer::DequeueBuffer(bool async, u32 width, u32 height, AndroidPixelFormat format, u32 usage, i32 &slot, std::optional<AndroidFence> &fence) {
if ((width && !height) || (!width && height)) {
state.logger->Warn("Dimensions {}x{} should be uniformly zero or non-zero", width, height);
return AndroidStatus::BadValue;
}
constexpr i32 invalidGraphicBufferSlot{-1}; //!< https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueCore.h;l=61
slot = invalidGraphicBufferSlot;
std::lock_guard guard(mutex);
auto result{state.gpu->presentation.GetFreeTexture(async, slot)};
if (result != AndroidStatus::Ok) [[unlikely]] {
if (result == AndroidStatus::Busy)
state.logger->Warn("No free buffers to dequeue");
return result;
}
width = width ? width : defaultWidth;
height = height ? height : defaultHeight;
format = (format != AndroidPixelFormat::None) ? format : defaultFormat;
u32 slot{state.gpu->presentation.GetFreeTexture()};
auto &buffer{queue.at(slot)}; 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) { if (!buffer.graphicBuffer) {
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); // Horizon OS doesn't ever allocate memory for the buffers on the GraphicBufferProducer end
// All buffers must be preallocated on the client application and attached to an Android buffer using SetPreallocatedBuffer
return AndroidStatus::NoMemory;
}
auto &surface{buffer.graphicBuffer->graphicHandle.surfaces.front()};
if (buffer.graphicBuffer->format != format || surface.width != width || surface.height != height || (buffer.graphicBuffer->usage & usage) != usage) {
state.logger->Warn("Buffer which has been dequeued isn't compatible with the supplied parameters: Dimensions: {}x{}={}x{}, Format: {}={}, Usage: 0x{:X}=0x{:X}", width, height, surface.width, surface.height, ToString(format), ToString(buffer.graphicBuffer->format), usage, buffer.graphicBuffer->usage);
// Nintendo doesn't deallocate the slot which was picked in here and reallocate it as a compatible buffer
// This is related to the comment above, Nintendo only allocates buffers on the client side
return AndroidStatus::NoInit;
} }
out.Push(slot); buffer.state = BufferState::Dequeued;
out.Push(std::array<u32, 13>{1, 0x24}); // Unknown fence = AndroidFence{}; // We just let the presentation engine return a buffer which is ready to be written into, there is no need for further synchronization
state.logger->Debug("#{} - Dimensions: {}x{}, Format: {}, Usage: 0x{:X}", slot, width, height, format, usage); state.logger->Debug("#{} - Dimensions: {}x{}, Format: {}, Usage: 0x{:X}, Is Async: {}", slot, width, height, ToString(format), usage, async);
return AndroidStatus::Ok;
} }
void GraphicBufferProducer::QueueBuffer(Parcel &in, Parcel &out) { AndroidStatus GraphicBufferProducer::QueueBuffer(i32 slot, i64 timestamp, bool isAutoTimestamp, AndroidRect crop, NativeWindowScalingMode scalingMode, NativeWindowTransform transform, NativeWindowTransform stickyTransform, bool async, u32 swapInterval, const AndroidFence &fence, u32 &width, u32 &height, NativeWindowTransform &transformHint, u32 &pendingBufferCount) {
struct Data { switch (scalingMode) {
u32 slot; case NativeWindowScalingMode::Freeze:
u64 timestamp; case NativeWindowScalingMode::ScaleToWindow:
u32 autoTimestamp; case NativeWindowScalingMode::ScaleCrop:
ARect crop; case NativeWindowScalingMode::NoScaleCrop:
u32 scalingMode; break;
u32 transform;
u32 stickyTransform;
u64 _unk0_;
u32 swapInterval;
std::array<nvdrv::Fence, 4> fence;
} &data{in.Pop<Data>()};
auto buffer{queue.at(data.slot)}; default:
buffer->texture->SynchronizeHost(); state.logger->Warn("{} is not a valid scaling mode", static_cast<u32>(scalingMode));
state.gpu->presentation.Present(buffer->texture); return AndroidStatus::BadValue;
}
std::lock_guard guard(mutex);
if (slot < 0 || slot >= queue.size()) [[unlikely]] {
state.logger->Warn("#{} was out of range", slot);
return AndroidStatus::BadValue;
}
auto &buffer{queue[slot]};
if (buffer.state != BufferState::Dequeued) [[unlikely]] {
state.logger->Warn("#{} was '{}' instead of being dequeued", slot, ToString(buffer.state));
return AndroidStatus::BadValue;
} else if (!buffer.wasBufferRequested) [[unlikely]] {
state.logger->Warn("#{} was queued prior to being requested", slot);
return AndroidStatus::BadValue;
}
auto graphicBuffer{*buffer.graphicBuffer};
if (graphicBuffer.width < (crop.right - crop.left) || graphicBuffer.height < (crop.bottom - crop.top)) [[unlikely]] {
state.logger->Warn("Crop was out of range for surface buffer: ({}-{})x({}-{}) > {}x{}", crop.left, crop.right, crop.top, crop.bottom, graphicBuffer.width, graphicBuffer.height);
return AndroidStatus::BadValue;
}
switch (transform) {
case NativeWindowTransform::Identity:
case NativeWindowTransform::MirrorHorizontal:
case NativeWindowTransform::MirrorVertical:
case NativeWindowTransform::Rotate90:
case NativeWindowTransform::Rotate180:
case NativeWindowTransform::Rotate270:
case NativeWindowTransform::MirrorHorizontalRotate90:
case NativeWindowTransform::MirrorVerticalRotate90:
case NativeWindowTransform::InvertDisplay:
break;
default:
throw exception("Application attempting to perform unknown transformation: {:#b}", static_cast<u32>(transform));
}
if (stickyTransform != NativeWindowTransform::Identity)
// We do not support sticky transforms as they are only used by the LEGACY camera mode
// Note: This is used on HOS to signal that the frame number should be returned but it's unimplemented
throw exception("Any non-identity sticky transform is not supported: '{}' ({:#b})", ToString(stickyTransform), static_cast<u32>(stickyTransform));
fence.Wait(state.soc->host1x);
buffer.texture->SynchronizeHost();
state.gpu->presentation.Present(slot);
state.gpu->presentation.bufferEvent->Signal(); state.gpu->presentation.bufferEvent->Signal();
struct { width = defaultWidth;
u32 width; height = defaultHeight;
u32 height; transformHint = state.gpu->presentation.GetTransformHint();
u32 _pad_[3]; pendingBufferCount = GetPendingBufferCount();
} output{
.width = buffer->gbpBuffer.width,
.height = buffer->gbpBuffer.height,
};
out.Push(output);
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); state.logger->Debug("#{} - {}Timestamp: {}, Crop: ({}-{})x({}-{}), Scale Mode: {}, Transform: {} [Sticky: {}], Swap Interval: {}, Is Async: {}", slot, isAutoTimestamp ? "Auto " : "", timestamp, crop.left, crop.right, crop.top, crop.bottom, ToString(scalingMode), ToString(transform), ToString(stickyTransform), swapInterval, async);
return AndroidStatus::Ok;
} }
void GraphicBufferProducer::CancelBuffer(Parcel &in) { void GraphicBufferProducer::CancelBuffer(i32 slot, const AndroidFence &fence) {
u32 slot{in.Pop<u32>()}; std::lock_guard guard(mutex);
//auto fences{in.Pop<std::array<nvdrv::Fence, 4>>()}; if (slot < 0 || slot >= queue.size()) [[unlikely]] {
state.logger->Warn("#{} was out of range", slot);
return;
}
// We cannot force the host GPU API to give us back a particular buffer due to how the swapchain works auto &buffer{queue[slot]};
// As a result of this, we just assume it'll be presented and dequeued at some point and not cancel the buffer here if (buffer.state != BufferState::Dequeued) [[unlikely]] {
state.logger->Warn("#{} is not owned by the producer as it is '{}' instead of being dequeued", slot, ToString(buffer.state));
return;
}
fence.Wait(state.soc->host1x);
state.gpu->presentation.Present(slot); // We use a present as a way to free the buffer so that it can be acquired in dequeueBuffer again
buffer.state = BufferState::Free;
buffer.frameNumber = 0;
state.gpu->presentation.bufferEvent->Signal();
state.logger->Debug("#{}", slot); state.logger->Debug("#{}", slot);
} }
void GraphicBufferProducer::Connect(Parcel &out) { AndroidStatus GraphicBufferProducer::Query(NativeWindowQuery query, u32 &out) {
struct { std::lock_guard guard(mutex);
u32 width{constant::HandheldResolutionW}; //!< The width of the display switch (query) {
u32 height{constant::HandheldResolutionH}; //!< The height of the display case NativeWindowQuery::Width:
u32 transformHint{}; //!< A hint of the transformation of the display out = defaultWidth;
u32 pendingBuffers{}; //!< The number of pending buffers break;
u32 status{}; //!< The status of the buffer queue
} data{}; case NativeWindowQuery::Height:
out.Push(data); out = defaultHeight;
state.logger->Debug("{}x{}", data.width, data.height); break;
case NativeWindowQuery::Format:
out = static_cast<u32>(defaultFormat);
break;
case NativeWindowQuery::MinUndequeuedBuffers:
// Calls into BufferQueueCore::getMinUndequeuedBufferCountLocked, which always returns mMaxAcquiredBufferCount (0) on HOS as UseAsyncBuffer is false due to HOS not using asynchronous buffers (No allocations on the server are supported)
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueCore.cpp;l=133-145
out = 0;
break;
case NativeWindowQuery::StickyTransform:
out = static_cast<u32>(NativeWindowTransform::Identity); // We don't support any sticky transforms, they're only used by the LEGACY camera mode
break;
case NativeWindowQuery::ConsumerRunningBehind:
out = false; // We have no way of knowing if the consumer is slower than the producer as we are not notified when a buffer has been acquired on the host
break;
case NativeWindowQuery::ConsumerUsageBits:
out = 0; // HOS layers (Consumers) have no Gralloc usage bits set
break;
case NativeWindowQuery::MaxBufferCount: {
// Calls into BufferQueueCore::getMaxBufferCountLocked, which will always return mDefaultMaxBufferCount (2 which is activeBufferCount's initial value) or mOverrideMaxBufferCount (activeBufferCount) as it's set during SetPreallocatedBuffer
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueCore.cpp;l=151-172
out = activeSlotCount;
break;
} }
void GraphicBufferProducer::SetPreallocatedBuffer(Parcel &in) { default:
struct Data { state.logger->Warn("Query not supported: {}", static_cast<u32>(query));
u32 slot; return AndroidStatus::BadValue;
u32 _unk0_; }
u32 length;
u32 _pad0_;
} &data = in.Pop<Data>();
auto &gbpBuffer{in.Pop<GbpBuffer>()}; state.logger->Debug("{}: {}", ToString(query), out);
return AndroidStatus::Ok;
}
AndroidStatus GraphicBufferProducer::Connect(NativeWindowApi api, bool producerControlledByApp, u32 &width, u32 &height, NativeWindowTransform &transformHint, u32 &pendingBufferCount) {
std::lock_guard guard(mutex);
if (connectedApi != NativeWindowApi::None) [[unlikely]] {
state.logger->Warn("Already connected to API '{}' while connection to '{}' is requested", ToString(connectedApi), ToString(api));
return AndroidStatus::BadValue;
}
switch (api) {
case NativeWindowApi::EGL:
case NativeWindowApi::CPU:
case NativeWindowApi::Media:
case NativeWindowApi::Camera:
break;
default:
state.logger->Warn("Unknown API: {}", static_cast<u32>(api));
return AndroidStatus::BadValue;
}
connectedApi = api;
width = defaultWidth;
height = defaultHeight;
transformHint = state.gpu->presentation.GetTransformHint();
pendingBufferCount = GetPendingBufferCount();
state.logger->Debug("API: {}, Producer Controlled By App: {}, Default Dimensions: {}x{}, Transform Hint: {}, Pending Buffer Count: {}", ToString(api), producerControlledByApp, width, height, ToString(transformHint), pendingBufferCount);
return AndroidStatus::Ok;
}
AndroidStatus GraphicBufferProducer::Disconnect(NativeWindowApi api) {
std::lock_guard guard(mutex);
switch (api) {
case NativeWindowApi::EGL:
case NativeWindowApi::CPU:
case NativeWindowApi::Media:
case NativeWindowApi::Camera:
break;
default:
state.logger->Warn("Unknown API: {}", static_cast<u32>(api));
return AndroidStatus::BadValue;
}
if (api != connectedApi) {
state.logger->Warn("Disconnecting from API '{}' while connected to '{}'", ToString(api), ToString(connectedApi));
return AndroidStatus::BadValue;
}
connectedApi = NativeWindowApi::None;
for (auto &slot : queue) {
slot.state = BufferState::Free;
slot.frameNumber = std::numeric_limits<u32>::max();
slot.graphicBuffer = nullptr;
}
state.logger->Debug("API: {}", ToString(api));
return AndroidStatus::Ok;
}
AndroidStatus GraphicBufferProducer::SetPreallocatedBuffer(i32 slot, const GraphicBuffer &graphicBuffer) {
std::lock_guard guard(mutex);
if (slot < 0 || slot >= MaxSlotCount) [[unlikely]] {
state.logger->Warn("#{} was out of range", slot);
return AndroidStatus::BadValue;
}
if (graphicBuffer.magic != GraphicBuffer::Magic)
throw exception("Unexpected GraphicBuffer magic: 0x{} (Expected: 0x{})", graphicBuffer.magic, GraphicBuffer::Magic);
else if (graphicBuffer.intCount != sizeof(NvGraphicHandle) / sizeof(u32))
throw exception("Unexpected GraphicBuffer native_handle integer count: 0x{} (Expected: 0x{})", graphicBuffer.intCount, sizeof(NvGraphicHandle));
gpu::texture::Format format;
switch (graphicBuffer.format) {
case AndroidPixelFormat::RGBA8888:
case AndroidPixelFormat::RGBX8888:
format = gpu::format::RGBA8888Unorm;
break;
case AndroidPixelFormat::RGB565:
format = gpu::format::RGB565Unorm;
break;
default:
throw exception("Unknown format in buffer: '{}' ({})", ToString(graphicBuffer.format), static_cast<u32>(graphicBuffer.format));
}
auto &handle{graphicBuffer.graphicHandle};
if (handle.magic != NvGraphicHandle::Magic)
throw exception("Unexpected NvGraphicHandle magic: {}", handle.surfaceCount);
else if (handle.surfaceCount < 1)
throw exception("At least one surface is required in a buffer: {}", handle.surfaceCount);
else if (handle.surfaceCount > 1)
throw exception("Multi-planar surfaces are not supported: {}", handle.surfaceCount);
auto &surface{graphicBuffer.graphicHandle.surfaces.at(0)};
if (surface.scanFormat != NvDisplayScanFormat::Progressive)
throw exception("Non-Progressive surfaces are not supported: {}", ToString(surface.scanFormat));
std::shared_ptr<nvdrv::device::NvMap::NvMapObject> nvBuffer{}; std::shared_ptr<nvdrv::device::NvMap::NvMapObject> nvBuffer{};
{
auto driver{nvdrv::driver.lock()}; auto driver{nvdrv::driver.lock()};
auto nvmap{driver->nvMap.lock()}; auto nvmap{driver->nvMap.lock()};
if (surface.nvmapHandle) {
if (gbpBuffer.nvmapHandle) { nvBuffer = nvmap->GetObject(surface.nvmapHandle);
nvBuffer = nvmap->GetObject(gbpBuffer.nvmapHandle);
} else { } else {
std::shared_lock nvmapLock(nvmap->mapMutex); std::shared_lock nvmapLock(nvmap->mapMutex);
for (const auto &object : nvmap->maps) { for (const auto &object : nvmap->maps) {
if (object->id == gbpBuffer.nvmapId) { if (object->id == handle.nvmapId) {
nvBuffer = object; nvBuffer = object;
break; break;
} }
} }
if (!nvBuffer) if (!nvBuffer)
throw exception("A QueueBuffer request has an invalid NVMap Handle ({}) and ID ({})", gbpBuffer.nvmapHandle, gbpBuffer.nvmapId); throw exception("A QueueBuffer request has an invalid NvMap Handle ({}) and ID ({})", surface.nvmapHandle, handle.nvmapId);
}
} }
gpu::texture::Format format; if (surface.size > (nvBuffer->size - surface.offset))
switch (gbpBuffer.format) { throw exception("Surface doesn't fit into NvMap mapping of size 0x{:X} when mapped at 0x{:X} -> 0x{:X}", nvBuffer->size, surface.offset, surface.offset + surface.size);
case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM: gpu::texture::TileMode tileMode;
format = gpu::format::RGBA8888Unorm; gpu::texture::TileConfig tileConfig;
break; if (surface.layout != NvSurfaceLayout::Blocklinear) {
case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM: tileMode = gpu::texture::TileMode::Block;
format = gpu::format::RGB565Unorm; tileConfig = {
break; .surfaceWidth = static_cast<u16>(surface.width),
default: .blockHeight = static_cast<u8>(1U << surface.blockHeightLog2),
throw exception("Unknown pixel format used for FB"); .blockDepth = 1,
};
} else if (surface.layout != NvSurfaceLayout::Pitch) {
tileMode = gpu::texture::TileMode::Pitch;
tileConfig = {
.pitch = surface.pitch,
};
} else if (surface.layout == NvSurfaceLayout::Tiled) {
throw exception("Legacy 16Bx16 tiled surfaces are not supported");
} }
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 + surface.offset, gpu::texture::Dimensions(surface.width, surface.height), format, tileMode, tileConfig)};
auto &buffer{queue[slot]};
buffer.state = BufferState::Free;
buffer.frameNumber = 0;
buffer.wasBufferRequested = false;
buffer.graphicBuffer = std::make_unique<GraphicBuffer>(graphicBuffer);
buffer.texture = state.gpu->presentation.CreatePresentationTexture(texture, slot);
activeSlotCount = hasBufferCount = std::count_if(queue.begin(), queue.end(), [](const BufferSlot &slot) { return static_cast<bool>(slot.graphicBuffer); });
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("#{} - 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); state.logger->Debug("#{} - Dimensions: {}x{} [Stride: {}], Format: {}, Layout: {}, {}: {}, Usage: 0x{:X}, NvMap {}: {}, Buffer Start/End: 0x{:X} -> 0x{:X}", slot, surface.width, surface.height, handle.stride, ToString(graphicBuffer.format), ToString(surface.layout), surface.layout == NvSurfaceLayout::Blocklinear ? "Block Height" : "Pitch", surface.layout == NvSurfaceLayout::Blocklinear ? 1U << surface.blockHeightLog2 : surface.pitch, graphicBuffer.usage, surface.nvmapHandle ? "Handle" : "ID", surface.nvmapHandle ? surface.nvmapHandle : handle.nvmapId, surface.offset, surface.offset + surface.size);
return AndroidStatus::Ok;
} }
void GraphicBufferProducer::OnTransact(TransactionCode code, Parcel &in, Parcel &out) { void GraphicBufferProducer::OnTransact(TransactionCode code, Parcel &in, Parcel &out) {
switch (code) { switch (code) {
case TransactionCode::RequestBuffer: case TransactionCode::RequestBuffer: {
RequestBuffer(in, out); GraphicBuffer *buffer{};
auto result{RequestBuffer(in.Pop<i32>(), buffer)};
out.PushOptionalFlattenable(buffer);
out.Push(result);
break; break;
case TransactionCode::DequeueBuffer: }
DequeueBuffer(in, out);
case TransactionCode::DequeueBuffer: {
i32 slot{};
std::optional<AndroidFence> fence{};
auto result{DequeueBuffer(in.Pop<u32>(), in.Pop<u32>(), in.Pop<u32>(), in.Pop<AndroidPixelFormat>(), in.Pop<u32>(), slot, fence)};
out.Push(slot);
out.PushOptionalFlattenable(fence);
out.Push(result);
break; break;
case TransactionCode::QueueBuffer: }
QueueBuffer(in, out);
case TransactionCode::QueueBuffer: {
u32 width{}, height{}, pendingBufferCount{};
NativeWindowTransform transformHint{};
constexpr u64 QueueBufferInputSize{0x54}; //!< The size of the QueueBufferInput structure (https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h;l=265-315)
auto slot{in.Pop<i32>()};
auto queueBufferInputSize{in.Pop<u64>()};
if (queueBufferInputSize != QueueBufferInputSize)
throw exception("The size of QueueBufferInput in the Parcel (0x{:X}) doesn't match the expected size (0x{:X})", queueBufferInputSize, QueueBufferInputSize);
QueueBuffer(slot, in.Pop<i64>(), in.Pop<u32>(), in.Pop<AndroidRect>(), in.Pop<NativeWindowScalingMode>(), in.Pop<NativeWindowTransform>(), in.Pop<NativeWindowTransform>(), in.Pop<u32>(), in.Pop<u32>(), in.Pop<AndroidFence>(), width, height, transformHint, pendingBufferCount);
out.Push(width);
out.Push(height);
out.Push(transformHint);
out.Push(pendingBufferCount);
break; break;
case TransactionCode::CancelBuffer: }
CancelBuffer(in);
case TransactionCode::CancelBuffer: {
CancelBuffer(in.Pop<i32>(), in.Pop<AndroidFence>());
break; break;
case TransactionCode::Query: }
out.Push<u64>(0);
case TransactionCode::Query: {
u32 queryOut{};
auto result{Query(in.Pop<NativeWindowQuery>(), queryOut)};
out.Push(queryOut);
out.Push(result);
break; break;
case TransactionCode::Connect: }
Connect(out);
case TransactionCode::Connect: {
auto hasProducerListener{static_cast<bool>(in.Pop<u32>())};
if (hasProducerListener)
throw exception("Callbacks using IProducerListener are not supported");
u32 width{}, height{}, pendingBufferCount{};
NativeWindowTransform transformHint{};
auto result{Connect(in.Pop<NativeWindowApi>(), in.Pop<u32>(), width, height, transformHint, pendingBufferCount)};
out.Push(width);
out.Push(height);
out.Push(transformHint);
out.Push(pendingBufferCount);
out.Push(result);
break; break;
case TransactionCode::Disconnect: }
case TransactionCode::Disconnect: {
auto result{Disconnect(in.Pop<NativeWindowApi>())};
out.Push(result);
break; break;
case TransactionCode::SetPreallocatedBuffer: }
SetPreallocatedBuffer(in);
case TransactionCode::SetPreallocatedBuffer: {
SetPreallocatedBuffer(in.Pop<i32>(), *in.PopOptionalFlattenable<GraphicBuffer>());
break; break;
}
default: default:
throw exception("An unimplemented transaction was called: {}", static_cast<u32>(code)); throw exception("An unimplemented transaction was called: {}", static_cast<u32>(code));
} }

View File

@ -1,49 +1,55 @@
// 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/)
// Copyright © 2005 The Android Open Source Project
// Copyright © 2019-2020 Ryujinx Team and Contributors
#pragma once #pragma once
#include <services/common/parcel.h> #include <services/common/parcel.h>
#include "android_types.h"
#include "native_window.h"
namespace skyline::gpu { namespace skyline::gpu {
class Texture; class Texture;
} }
namespace skyline::service::hosbinder { #define ENUM_CASE(key) \
/** case ENUM_TYPE::key: \
* @brief A descriptor for the surfaceflinger graphics buffer return #key
* @url https://github.com/reswitched/libtransistor/blob/0f0c36227842c344d163922fc98ee76229e9f0ee/lib/display/graphic_buffer_queue.c#L66
*/ #define ENUM_STRING(name, cases) \
struct GbpBuffer { constexpr const char *ToString(name value) { \
u32 magic; //!< The magic of the graphics buffer: 0x47424652 using ENUM_TYPE = name; \
u32 width; //!< The width of the buffer switch (value) { \
u32 height; //!< The height of the buffer cases \
u32 stride; //!< The stride of the buffer default: \
u32 format; //!< The format of the buffer, this corresponds to AHardwareBuffer_Format return "Unknown"; \
u32 usage; //!< The usage flags for the buffer }; \
u32 _pad0_;
u32 index; //!< The index of the buffer
u32 _pad1_[3];
u32 nvmapId; //!< The ID of the buffer in regards to /dev/nvmap
u32 _pad2_[8];
u32 size; //!< The size of the buffer
u32 _pad3_[8];
u32 nvmapHandle; //!< The handle of the buffer in regards to /dev/nvmap
u32 offset; //!< The offset of the pixel data in the GPU Buffer
u32 _pad4_;
u32 blockHeightLog2; //!< The log2 of the block height
u32 _pad5_[58];
}; };
namespace skyline::service::hosbinder {
/** /**
* @brief A wrapper over GbpBuffer which contains additional state that we track for a buffer * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferSlot.h;l=52-91
*/ */
class Buffer { enum class BufferState {
public: Free,
std::shared_ptr<gpu::Texture> texture; Dequeued,
GbpBuffer gbpBuffer; Queued,
Acquired,
};
Buffer(const GbpBuffer &gbpBuffer, std::shared_ptr<gpu::Texture> texture); ENUM_STRING(BufferState, ENUM_CASE(Free);ENUM_CASE(Dequeued);ENUM_CASE(Queued);ENUM_CASE(Acquired);
);
/**
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferSlot.h;l=32-138
*/
struct BufferSlot {
BufferState state{BufferState::Free};
u64 frameNumber{}; //!< The amount of frames that have been queued using this slot
bool wasBufferRequested{}; //!< If GraphicBufferProducer::RequestBuffer has been called with this buffer
std::shared_ptr<gpu::Texture> texture{};
std::unique_ptr<GraphicBuffer> graphicBuffer{};
}; };
/** /**
@ -65,80 +71,123 @@ namespace skyline::service::hosbinder {
}; };
/** /**
* @brief IGraphicBufferProducer is responsible for presenting buffers to the display as well as compositing and frame pacing * @brief An endpoint for the GraphicBufferProducer interface, it approximately implements BufferQueueProducer but also implements the functionality of interfaces called into by it such as GraphicBufferConsumer, Gralloc and so on
* @url https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/IGraphicBufferProducer.cpp
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueCore.h
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueCore.cpp
*/ */
class GraphicBufferProducer { class GraphicBufferProducer {
private: private:
const DeviceState &state; const DeviceState &state;
std::vector<std::shared_ptr<Buffer>> queue; //!< A vector of shared pointers to all the queued buffers std::mutex mutex; //!< Synchronizes access to the buffer queue
constexpr static u8 MaxSlotCount{16}; //!< The maximum amount of buffer slots that a buffer queue can hold, Android supports 64 but they go unused for applications like games so we've lowered this to 16
std::array<BufferSlot, MaxSlotCount> queue;
u8 activeSlotCount{2}; //!< The amount of slots in the queue that can be used
u8 hasBufferCount{}; //!< The amount of slots with buffers attached in the queue
u32 defaultWidth{1}; //!< The assumed width of a buffer if none is supplied in DequeueBuffer
u32 defaultHeight{1}; //!< The assumed height of a buffer if none is supplied in DequeueBuffer
AndroidPixelFormat defaultFormat{AndroidPixelFormat::RGBA8888}; //!< The assumed format of a buffer if none is supplied in DequeueBuffer
NativeWindowApi connectedApi{NativeWindowApi::None}; //!< The API that the producer is currently connected to
/** /**
* @brief Request for the GbpBuffer of a buffer * @return The amount of buffers which have been queued onto the consumer
*/ */
void RequestBuffer(Parcel &in, Parcel &out); u8 GetPendingBufferCount();
/** /**
* @brief Try to dequeue a free graphics buffer that has been consumed * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h;l=67-80;
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h;l=35-40
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp;l=50-73
*/ */
void DequeueBuffer(Parcel &in, Parcel &out); AndroidStatus RequestBuffer(i32 slot, GraphicBuffer *&buffer);
/** /**
* @brief Queue a buffer to be presented * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h;l=104-170
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h;l=59-97
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp;l=251-388
*/ */
void QueueBuffer(Parcel &in, Parcel &out); AndroidStatus DequeueBuffer(bool async, u32 width, u32 height, AndroidPixelFormat format, u32 usage, i32 &slot, std::optional<AndroidFence> &fence);
/** /**
* @brief Remove a previously queued buffer * @note Nintendo has added an additional field for swap interval which sets the swap interval of the compositor
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h;l=236-349
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h;l=109-125
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp;l=512-691
*/ */
void CancelBuffer(Parcel &in); AndroidStatus QueueBuffer(i32 slot, i64 timestamp, bool isAutoTimestamp, AndroidRect crop, NativeWindowScalingMode scalingMode, NativeWindowTransform transform, NativeWindowTransform stickyTransform, bool async, u32 swapInterval, const AndroidFence &fence, u32 &width, u32 &height, NativeWindowTransform &transformHint, u32 &pendingBufferCount);
/** /**
* @brief Query a few attributes of the graphic buffers * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h;l=351-359
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h;l=127-132
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp;l=693-720
*/ */
void Connect(Parcel &out); void CancelBuffer(i32 slot, const AndroidFence &fence);
/** /**
* @brief Attach a GPU buffer to a graphics buffer * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h;l=361-367
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h;l=134-136
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp;l=722-766
*/ */
void SetPreallocatedBuffer(Parcel &in); AndroidStatus Query(NativeWindowQuery query, u32 &out);
/**
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h;l=369-405
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h;l=138-148
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp;l=768-831
*/
AndroidStatus Connect(NativeWindowApi api, bool producerControlledByApp, u32 &width, u32 &height, NativeWindowTransform &transformHint, u32 &pendingBufferCount);
/**
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h;l=407-426
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h;l=150-158
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp;l=833-890
*/
AndroidStatus Disconnect(NativeWindowApi api);
/**
* @brief Similar to AttachBuffer but the slot is explicitly specified and the producer defaults are set based off it
* @note This is an HOS-specific addition to GraphicBufferProducer, it exists so that all allocation of buffers is handled by the client to avoid any shared/transfer memory from the client to loan memory for the buffers which would be quite complicated
*/
AndroidStatus SetPreallocatedBuffer(i32 slot, const GraphicBuffer &graphicBuffer);
public: public:
DisplayId displayId{DisplayId::Null}; //!< The ID of this display DisplayId displayId{DisplayId::Null}; //!< The ID of this display
LayerStatus layerStatus{LayerStatus::Uninitialized}; //!< The status of the single layer the display has LayerStatus layerStatus{LayerStatus::Uninitialized}; //!< The status of the single layer the display has
/** /**
* @brief The functions called by TransactParcel for android.gui.IGraphicBufferProducer * @brief The transactions supported by android.gui.IGraphicBufferProducer
* @refitem https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#35 * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/IGraphicBufferProducer.cpp;l=35-49
*/ */
enum class TransactionCode : u32 { enum class TransactionCode : u32 {
RequestBuffer = 1, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#281 RequestBuffer = 1,
SetBufferCount = 2, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#293 SetBufferCount = 2,
DequeueBuffer = 3, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#300 DequeueBuffer = 3,
DetachBuffer = 4, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#318 DetachBuffer = 4,
DetachNextBuffer = 5, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#325 DetachNextBuffer = 5,
AttachBuffer = 6, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#343 AttachBuffer = 6,
QueueBuffer = 7, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#353 QueueBuffer = 7,
CancelBuffer = 8, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#364 CancelBuffer = 8,
Query = 9, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#372 Query = 9,
Connect = 10, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#381 Connect = 10,
Disconnect = 11, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#396 Disconnect = 11,
SetSidebandStream = 12, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#403 SetSidebandStream = 12,
AllocateBuffers = 13, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#413 AllocateBuffers = 13,
SetPreallocatedBuffer = 14, //!< No source on this but it's used to set a existing buffer according to libtransistor and libnx SetPreallocatedBuffer = 14, //!< A transaction specific to HOS, see the implementation for a description of its functionality
}; };
GraphicBufferProducer(const DeviceState &state); GraphicBufferProducer(const DeviceState &state);
/** /**
* @brief The handler for Binder IPC transactions with IGraphicBufferProducer * @brief The handler for Binder IPC transactions with IGraphicBufferProducer
* @url https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#277 * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/IGraphicBufferProducer.cpp;l=277-426
*/ */
void OnTransact(TransactionCode code, Parcel &in, Parcel &out); void OnTransact(TransactionCode code, Parcel &in, Parcel &out);
/** /**
* @brief Sets displayId to a specific display type * @brief Sets displayId to a specific display type
* @param name The name of the display
* @note displayId has to be DisplayId::Null or this will throw an exception * @note displayId has to be DisplayId::Null or this will throw an exception
*/ */
void SetDisplay(const std::string &name); void SetDisplay(const std::string &name);
@ -151,3 +200,5 @@ namespace skyline::service::hosbinder {
extern std::weak_ptr<GraphicBufferProducer> producer; //!< A globally shared instance of the GraphicsBufferProducer extern std::weak_ptr<GraphicBufferProducer> producer; //!< A globally shared instance of the GraphicsBufferProducer
} }
#undef ENUM_CASE

View File

@ -0,0 +1,234 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
// Copyright © 2005 The Android Open Source Project
// Copyright © 2019-2020 Ryujinx Team and Contributors
#pragma once
#include <common.h>
#include <soc/host1x.h>
#include <services/common/fence.h>
#define ENUM_CASE(name, key) \
case name::key: \
return #key
namespace skyline::service::hosbinder {
/**
* @brief An enumeration of all status codes for Android including Binder IPC
* @note We don't want to depend on POSIX <errno.h> so we just resolve all macros to their numerical values
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:system/core/include/utils/Errors.h
*/
enum class AndroidStatus : i32 {
Ok = 0,
UnknownError = std::numeric_limits<i32>::min(),
NoMemory = -12,
InvalidOperation = -38,
BadValue = -22,
BadType = UnknownError + 1,
NameNotFound = -2,
PermissionDenied = -1,
NoInit = -19,
AlreadyExists = -17,
DeadObject = -32,
FailedTransaction = UnknownError + 2,
JParksBrokeIt = -32,
BadIndex = -75,
NotEnoughData = -61,
WouldBlock = -11,
TimedOut = -110,
UnknownTransaction = -74,
FdsNotAllowed = UnknownError + 7,
Busy = -16, //!< An alias for -EBUSY which is used in BufferQueueProducer
};
/**
* @brief Nvidia and Nintendo's Android fence implementation, this significantly differs from the Android implementation (All FDs are inlined as integers rather than explicitly passed as FDs) but is a direct replacement
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/ui/Fence.h
*/
struct AndroidFence {
u32 fenceCount{}; //!< The amount of active fences in the array
std::array<nvdrv::Fence, 4> fences{}; //!< Nvidia's Android fence can hold a maximum of 4 fence FDs
AndroidFence() : fenceCount(0) {}
/**
* @brief Wait on all native fences in this Android fence till they're signalled
*/
void Wait(soc::host1x::Host1X &host1x) const {
if (fenceCount > fences.size())
throw exception("Wait has larger fence count ({}) than storage size ({})", fenceCount, fences.size());
for (auto it{fences.begin()}, end{fences.begin() + fenceCount}; it < end; it++)
if (!host1x.syncpoints.at(it->id).Wait(it->value, std::chrono::steady_clock::duration::max()))
throw exception("Waiting on native fence #{} (Host1X Syncpoint: {}) has timed out", std::distance(fences.begin(), it), it->id);
}
};
/**
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/android/rect.h
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/ui/Rect.h
* @note We use unsigned values rather than signed as this makes it easier to error check, negative values are not valid in any location we use them in
*/
struct AndroidRect {
u32 left;
u32 top;
u32 right;
u32 bottom;
};
/**
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/ui/PixelFormat.h;l=35-68
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:system/core/include/system/graphics.h;l=44-321
*/
enum class AndroidPixelFormat {
None = 0,
Custom = -4,
Translucent = -3,
Transparent = -2,
Opaque = -1,
RGBA8888 = 1, //! 4x8-bit RGBA
RGBX8888 = 2, //! 4x8-bit RGB0
RGB888 = 3, //! 3x8-bit RGB
RGB565 = 4, //! 16-bit RGB
BGRA8888 = 5, //! 4x8-bit BGRA
RGBA5551 = 6, //! 16-bit ARGB
RGBA4444 = 7, //! 16-bit ARGB
sRGBA8888 = 12, //! 4x8-bit sRGB + A
sRGBX8888 = 13, //! 4x8-bit sRGB + 0
};
constexpr const char *ToString(AndroidPixelFormat format) {
switch (format) {
ENUM_CASE(AndroidPixelFormat, None);
ENUM_CASE(AndroidPixelFormat, Custom);
ENUM_CASE(AndroidPixelFormat, Translucent);
ENUM_CASE(AndroidPixelFormat, Transparent);
ENUM_CASE(AndroidPixelFormat, Opaque);
ENUM_CASE(AndroidPixelFormat, RGBA8888);
ENUM_CASE(AndroidPixelFormat, RGBX8888);
ENUM_CASE(AndroidPixelFormat, RGB888);
ENUM_CASE(AndroidPixelFormat, RGB565);
ENUM_CASE(AndroidPixelFormat, BGRA8888);
ENUM_CASE(AndroidPixelFormat, RGBA5551);
ENUM_CASE(AndroidPixelFormat, RGBA4444);
ENUM_CASE(AndroidPixelFormat, sRGBA8888);
ENUM_CASE(AndroidPixelFormat, sRGBX8888);
default:
return "Unknown";
}
}
/**
* @brief The layout of the surface's pixels in GPU memory
*/
enum class NvSurfaceLayout : u32 {
Pitch = 0x1, //!< A linear pixel arrangement but rows aligned to the pitch
Tiled = 0x2, //!< A legacy 16Bx16 block layout which was used in NVENC prior to being deprecated
Blocklinear = 0x3, //!< A generic block layout which is further defined by it's kind
};
constexpr const char *ToString(NvSurfaceLayout layout) {
switch (layout) {
ENUM_CASE(NvSurfaceLayout, Pitch);
ENUM_CASE(NvSurfaceLayout, Tiled);
ENUM_CASE(NvSurfaceLayout, Blocklinear);
default:
return "Unknown";
}
}
/**
* @brief The kind of tiling used to arrange pixels in a blocklinear surface
*/
enum class NvKind : u32 {
Pitch = 0x0,
Generic16Bx2 = 0xFE, //!< A block layout with sector width of 16 and sector height as 2 (16Bx2)
Invalid = 0xFF,
};
/**
* @brief The format in which the surface is scanned out to a display
*/
enum class NvDisplayScanFormat : u32 {
Progressive, //!< All rows of the image are updated at once
Interlaced, //!< Odd and even rows are updated in an alternating pattern
};
constexpr const char *ToString(NvDisplayScanFormat format) {
switch (format) {
ENUM_CASE(NvDisplayScanFormat, Progressive);
ENUM_CASE(NvDisplayScanFormat, Interlaced);
default:
return "Unknown";
}
}
#pragma pack(push, 1)
/**
* @brief All metadata about a single surface, most of this will mirror the data in NvGraphicHandle and GraphicBuffer
*/
struct NvSurface {
u32 width;
u32 height;
u64 format; //!< The internal format of the surface
NvSurfaceLayout layout;
u32 pitch; //!< The pitch of the surface for pitch-linear surfaces
u32 nvmapHandle; //!< The handle of the buffer containing this surface in regards to /dev/nvmap
u32 offset; //!< The offset of the surface into the buffer
NvKind kind;
u32 blockHeightLog2; //!< The log2 of the block height in blocklinear surfaces
NvDisplayScanFormat scanFormat;
u32 oddRowOffset; //!< The offset of all odd rows relative to the start of the buffer
u64 flags;
u64 size;
u32 _unk_[6];
};
static_assert(sizeof(NvSurface) == 0x58);
/**
* @brief The integers of the native_handle used by Nvidia to marshall the surfaces in this buffer
*/
struct NvGraphicHandle {
constexpr static u32 Magic{0xDAFFCAFF};
u32 _unk0_; //!< This is presumably a file descriptor that Nintendo removed as it's value is always a null FD (-1)
u32 nvmapId; //!< The ID of the buffer in regards to /dev/nvmap
u32 _unk1_;
u32 magic; //!< The magic for the buffer (0xDAFFCAFF)
u32 ownerPid; //!< Same as the upper 32-bits of the ID in the GraphicBuffer (0x2F)
u32 type;
u32 usage; //!< The Gralloc usage flags, same as GraphicBuffer
u32 format; //!< The internal format of the buffer
u32 externalFormat; //!< The external format that's exposed by Gralloc
u32 stride;
u32 size; //!< The size of the buffer in bytes
u32 surfaceCount; //!< The amount of valid surfaces in the array
u32 _unk2_;
std::array<NvSurface, 3> surfaces;
u32 _unk3_[2];
};
static_assert(sizeof(NvGraphicHandle) == 0x144);
/**
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/ui/GraphicBuffer.h
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/ui/GraphicBuffer.cpp;l=266-301
*/
struct GraphicBuffer {
constexpr static u32 Magic{'GBFR'}; //!< The magic is in little-endian, we do not need to use 'util::MakeMagic'
u32 magic; //!< The magic of the Graphics BuFfeR: 'GBFR' (0x47424652)
u32 width;
u32 height;
u32 stride;
AndroidPixelFormat format;
u32 usage; //!< The Gralloc usage flags for the buffer, this is a deprecated 32-bit usage flag
u64 id; //!< A 64-bit ID composed of a 32-bit PID and 32-bit incrementing counter
u32 fdCount; //!< The amount of FDs being transferred alongside this buffer, NN uses none so this should be 0
u32 intCount; //!< The size of the native buffer in 32-bit integer units, should be equal to the size of NvNativeHandle in 32-bit units
NvGraphicHandle graphicHandle;
};
static_assert(sizeof(GraphicBuffer) == 0x16C);
#pragma pack(pop)
}
#undef ENUM_CASE

View File

@ -0,0 +1,127 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
// Copyright © 2011 The Android Open Source Project
#pragma once
#define ENUM_CASE(name, key) \
case name::key: \
return #key
namespace skyline::service::hosbinder {
/**
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:system/core/include/system/window.h;l=300-318
*/
enum class NativeWindowApi : u32 {
None = 0,
EGL = 1, //!< All GPU presentation APIs including EGL, Vulkan and NVN conform to this
CPU = 2,
Media = 3,
Camera = 4,
};
constexpr const char *ToString(NativeWindowApi api) {
switch (api) {
ENUM_CASE(NativeWindowApi, None);
ENUM_CASE(NativeWindowApi, EGL);
ENUM_CASE(NativeWindowApi, CPU);
ENUM_CASE(NativeWindowApi, Media);
ENUM_CASE(NativeWindowApi, Camera);
default:
return "Unknown";
}
}
/**
* @note A few combinations of transforms that are not in the NATIVE_WINDOW_TRANSFORM enum were added to assist with conversion to/from Vulkan transforms
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:system/core/include/system/window.h;l=321-335
*/
enum class NativeWindowTransform : u32 {
Identity = 0b0,
MirrorHorizontal = 0b1,
MirrorVertical = 0b10,
Rotate90 = 0b100,
Rotate180 = MirrorHorizontal | MirrorVertical,
Rotate270 = Rotate180 | Rotate90,
MirrorHorizontalRotate90 = MirrorHorizontal | Rotate90,
MirrorVerticalRotate90 = MirrorVertical | Rotate90,
InvertDisplay = 0b1000,
};
constexpr const char *ToString(NativeWindowTransform transform) {
switch (transform) {
ENUM_CASE(NativeWindowTransform, Identity);
ENUM_CASE(NativeWindowTransform, MirrorHorizontal);
ENUM_CASE(NativeWindowTransform, MirrorVertical);
ENUM_CASE(NativeWindowTransform, Rotate90);
ENUM_CASE(NativeWindowTransform, Rotate180);
ENUM_CASE(NativeWindowTransform, Rotate270);
ENUM_CASE(NativeWindowTransform, MirrorHorizontalRotate90);
ENUM_CASE(NativeWindowTransform, MirrorVerticalRotate90);
ENUM_CASE(NativeWindowTransform, InvertDisplay);
default:
return "Unknown";
}
}
/**
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:system/core/include/system/window.h;l=338-354
*/
enum class NativeWindowScalingMode : u32 {
Freeze = 0,
ScaleToWindow = 1,
ScaleCrop = 2,
NoScaleCrop = 3,
};
constexpr const char *ToString(NativeWindowScalingMode scalingMode) {
switch (scalingMode) {
ENUM_CASE(NativeWindowScalingMode, Freeze);
ENUM_CASE(NativeWindowScalingMode, ScaleToWindow);
ENUM_CASE(NativeWindowScalingMode, ScaleCrop);
ENUM_CASE(NativeWindowScalingMode, NoScaleCrop);
default:
return "Unknown";
}
}
/**
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:system/core/include/system/window.h;l=127-265
*/
enum class NativeWindowQuery : u32 {
Width = 0,
Height = 1,
Format = 2,
MinUndequeuedBuffers = 3,
QueuesToWindowComposer = 4,
ConcreteType = 5,
DefaultWidth = 6,
DefaultHeight = 7,
TransformHint = 8,
ConsumerRunningBehind = 9,
ConsumerUsageBits = 10,
StickyTransform = 11,
MaxBufferCount = 12, //!< A custom query for HOS which returns the maximum number of buffers that can be allocated at once
};
constexpr const char *ToString(NativeWindowQuery query) {
switch (query) {
ENUM_CASE(NativeWindowQuery, Width);
ENUM_CASE(NativeWindowQuery, Height);
ENUM_CASE(NativeWindowQuery, Format);
ENUM_CASE(NativeWindowQuery, MinUndequeuedBuffers);
ENUM_CASE(NativeWindowQuery, QueuesToWindowComposer);
ENUM_CASE(NativeWindowQuery, ConcreteType);
ENUM_CASE(NativeWindowQuery, DefaultWidth);
ENUM_CASE(NativeWindowQuery, DefaultHeight);
ENUM_CASE(NativeWindowQuery, TransformHint);
ENUM_CASE(NativeWindowQuery, ConsumerRunningBehind);
ENUM_CASE(NativeWindowQuery, ConsumerUsageBits);
ENUM_CASE(NativeWindowQuery, StickyTransform);
default:
return "Unknown";
}
}
}
#undef ENUM_CASE

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: LGPL-3.0-or-later // 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/)
#pragma once #pragma once

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: LGPL-3.0-or-later // SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once #pragma once

View File

@ -18,7 +18,7 @@ namespace skyline::soc::gm20b::engine {
/** /**
* @url https://github.com/NVIDIA/open-gpu-doc/blob/ab27fc22db5de0d02a4cabe08e555663b62db4d4/classes/host/clb06f.h#L65 * @url https://github.com/NVIDIA/open-gpu-doc/blob/ab27fc22db5de0d02a4cabe08e555663b62db4d4/classes/host/clb06f.h#L65
*/ */
#pragma pack(push, 1) #pragma pack(push, 1)
union Registers { union Registers {
std::array<u32, RegisterCount> raw; std::array<u32, RegisterCount> raw;
@ -162,7 +162,7 @@ namespace skyline::soc::gm20b::engine {
}; };
} registers{}; } registers{};
static_assert(sizeof(Registers) == (RegisterCount * sizeof(u32))); static_assert(sizeof(Registers) == (RegisterCount * sizeof(u32)));
#pragma pack(pop) #pragma pack(pop)
public: public:
GPFIFO(const DeviceState &state) : Engine(state) {} GPFIFO(const DeviceState &state) : Engine(state) {}

View File

@ -13,7 +13,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
*/ */
class MacroInterpreter { class MacroInterpreter {
private: private:
#pragma pack(push, 1) #pragma pack(push, 1)
union Opcode { union Opcode {
u32 raw; u32 raw;
@ -89,8 +89,8 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
} }
} bitfield; } bitfield;
}; };
#pragma pack(pop)
static_assert(sizeof(Opcode) == sizeof(u32)); static_assert(sizeof(Opcode) == sizeof(u32));
#pragma pack(pop)
/** /**
* @brief Metadata about the Maxwell 3D method to be called in 'Send' * @brief Metadata about the Maxwell 3D method to be called in 'Send'

View File

@ -33,7 +33,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
/** /**
* @url https://github.com/devkitPro/deko3d/blob/master/source/maxwell/engine_3d.def#L478 * @url https://github.com/devkitPro/deko3d/blob/master/source/maxwell/engine_3d.def#L478
*/ */
#pragma pack(push, 1) #pragma pack(push, 1)
union Registers { union Registers {
std::array<u32, RegisterCount> raw; std::array<u32, RegisterCount> raw;
@ -552,7 +552,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
}; };
}; };
static_assert(sizeof(Registers) == (RegisterCount * sizeof(u32))); static_assert(sizeof(Registers) == (RegisterCount * sizeof(u32)));
#pragma pack(pop) #pragma pack(pop)
Registers registers{}; Registers registers{};
Registers shadowRegisters{}; //!< The shadow registers, their function is controlled by the 'shadowRamControl' register Registers shadowRegisters{}; //!< The shadow registers, their function is controlled by the 'shadowRamControl' register

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: LGPL-3.0-or-later // 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/)
#pragma once #pragma once

View File

@ -1,5 +1,6 @@
// 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/)
// Copyright © 2020 Ryujinx Team and Contributors
#include "syncpoint.h" #include "syncpoint.h"
@ -38,13 +39,15 @@ namespace skyline::soc::host1x {
} }
bool Syncpoint::Wait(u32 threshold, std::chrono::steady_clock::duration timeout) { bool Syncpoint::Wait(u32 threshold, std::chrono::steady_clock::duration timeout) {
if (value >= threshold)
return true;
std::mutex mtx; std::mutex mtx;
std::condition_variable cv; std::condition_variable cv;
bool flag{}; bool flag{};
if (timeout == std::chrono::steady_clock::duration::max()) if (timeout == std::chrono::steady_clock::duration::max())
timeout = std::chrono::seconds(1); timeout = std::chrono::seconds(1);
if (!RegisterWaiter(threshold, [&cv, &mtx, &flag] { if (!RegisterWaiter(threshold, [&cv, &mtx, &flag] {
std::unique_lock lock(mtx); std::unique_lock lock(mtx);
flag = true; flag = true;

View File

@ -1,5 +1,6 @@
// 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/)
// Copyright © 2020 Ryujinx Team and Contributors
#pragma once #pragma once
@ -45,7 +46,7 @@ namespace skyline::soc::host1x {
/** /**
* @brief Waits for the syncpoint to reach given threshold * @brief Waits for the syncpoint to reach given threshold
* @return false if the timeout was reached, otherwise true * @return If the wait was successful (true) or timed out (false)
*/ */
bool Wait(u32 threshold, std::chrono::steady_clock::duration timeout); bool Wait(u32 threshold, std::chrono::steady_clock::duration timeout);
}; };