mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-26 13:01:57 +01:00
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:
parent
da7e18de4c
commit
d8025e7178
@ -119,11 +119,6 @@ namespace skyline {
|
||||
};
|
||||
|
||||
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
|
||||
constexpr u64 NsInSecond{1000000000}; //!< The amount of nanoseconds in a second
|
||||
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
|
||||
*/
|
||||
template<typename T>
|
||||
T PointerValue(T item) {
|
||||
constexpr T PointerValue(T 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
|
||||
*/
|
||||
template<typename Return, typename T>
|
||||
Return ValuePointer(T item) {
|
||||
constexpr Return ValuePointer(T item) {
|
||||
if constexpr (std::is_pointer<Return>::value)
|
||||
return reinterpret_cast<Return>(item);
|
||||
else
|
||||
|
@ -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/)
|
||||
|
||||
#include <jvm.h>
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
#include <android/native_window_jni.h>
|
||||
#include <gpu.h>
|
||||
#include "jvm.h"
|
||||
#include <jvm.h>
|
||||
#include "presentation_engine.h"
|
||||
|
||||
extern skyline::i32 Fps;
|
||||
@ -22,7 +22,30 @@ namespace skyline::gpu {
|
||||
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)
|
||||
return;
|
||||
|
||||
@ -36,6 +59,8 @@ namespace skyline::gpu {
|
||||
if ((capabilities.supportedUsageFlags & presentUsage) != presentUsage)
|
||||
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{
|
||||
.surface = **vkSurface,
|
||||
.minImageCount = imageCount,
|
||||
@ -59,7 +84,7 @@ namespace skyline::gpu {
|
||||
}
|
||||
|
||||
void PresentationEngine::UpdateSurface(jobject newSurface) {
|
||||
std::lock_guard lock(mutex);
|
||||
std::lock_guard guard(mutex);
|
||||
|
||||
auto env{state.jvm->GetEnv()};
|
||||
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::unique_lock lock(mutex);
|
||||
std::lock_guard guard(mutex);
|
||||
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)));
|
||||
}
|
||||
|
||||
u32 PresentationEngine::GetFreeTexture() {
|
||||
service::hosbinder::AndroidStatus PresentationEngine::GetFreeTexture(bool async, i32 &slot) {
|
||||
using AndroidStatus = service::hosbinder::AndroidStatus;
|
||||
|
||||
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();
|
||||
if (swapchain.dequeuedCount < swapchain.imageCount) {
|
||||
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(); });
|
||||
return GetFreeTexture(async, slot);
|
||||
}
|
||||
|
||||
gpu.vkDevice.waitForFences(*fence, true, std::numeric_limits<u64>::max());
|
||||
|
||||
slot = nextImage.second;
|
||||
return AndroidStatus::Ok;
|
||||
}
|
||||
return nextImage.second;
|
||||
return AndroidStatus::Busy;
|
||||
}
|
||||
|
||||
void PresentationEngine::Present(const std::shared_ptr<Texture> &texture) {
|
||||
void PresentationEngine::Present(i32 slot) {
|
||||
std::unique_lock lock(mutex);
|
||||
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();
|
||||
|
||||
if (frameTimestamp) {
|
||||
@ -119,4 +163,12 @@ namespace skyline::gpu {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
#include <common/trace.h>
|
||||
#include <kernel/types/KEvent.h>
|
||||
#include <services/hosbinder/native_window.h>
|
||||
#include <services/hosbinder/android_types.h>
|
||||
#include "texture.h"
|
||||
|
||||
struct ANativeWindow;
|
||||
@ -18,13 +20,15 @@ namespace skyline::gpu {
|
||||
const DeviceState &state;
|
||||
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
|
||||
std::condition_variable surfaceCondition; //!< Allows us to efficiently wait for Vulkan surface to be initialized
|
||||
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<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
|
||||
struct SwapchainContext {
|
||||
u32 imageCount{};
|
||||
u16 imageCount{};
|
||||
i32 dequeuedCount{};
|
||||
vk::Format imageFormat{};
|
||||
vk::Extent2D imageExtent{};
|
||||
} 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
|
||||
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:
|
||||
texture::Dimensions resolution{};
|
||||
@ -55,13 +59,19 @@ namespace skyline::gpu {
|
||||
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();
|
||||
};
|
||||
}
|
||||
|
@ -94,9 +94,9 @@ namespace skyline::gpu {
|
||||
* @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
|
||||
Linear, //!< All pixels are arranged linearly
|
||||
Pitch, //!< All pixels are arranged linearly but rows aligned to the pitch
|
||||
Block, //!< All pixels are arranged into blocks and swizzled in a Z-order curve to optimize for spacial locality
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -67,7 +67,7 @@ namespace skyline::kernel::type {
|
||||
munmap(kernel.ptr, kernel.size);
|
||||
|
||||
if (state.process && guest.Valid()) {
|
||||
mmap(guest.ptr, guest.size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); // As this is the destructor, we cannot throw on this failing
|
||||
mmap(guest.ptr, guest.size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); // As this is the destructor, we cannot throw on this failing
|
||||
state.process->memory.InsertChunk(ChunkDescriptor{
|
||||
.ptr = guest.ptr,
|
||||
.size = guest.size,
|
||||
|
@ -54,11 +54,15 @@ namespace skyline::service::am {
|
||||
|
||||
Result ICommonStateGetter::GetDefaultDisplayResolution(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
if (operationMode == OperationMode::Handheld) {
|
||||
response.Push<u32>(constant::HandheldResolutionW);
|
||||
response.Push<u32>(constant::HandheldResolutionH);
|
||||
constexpr u16 HandheldResolutionW{1280};
|
||||
constexpr u16 HandheldResolutionH{720};
|
||||
response.Push<u32>(HandheldResolutionW);
|
||||
response.Push<u32>(HandheldResolutionH);
|
||||
} else if (operationMode == OperationMode::Docked) {
|
||||
response.Push<u32>(constant::DockedResolutionW);
|
||||
response.Push<u32>(constant::DockedResolutionH);
|
||||
constexpr u16 DockedResolutionW{1920};
|
||||
constexpr u16 DockedResolutionH{1080};
|
||||
response.Push<u32>(DockedResolutionW);
|
||||
response.Push<u32>(DockedResolutionH);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
@ -49,16 +49,52 @@ namespace skyline::service {
|
||||
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
|
||||
*/
|
||||
template<typename ValueType>
|
||||
void Push(const ValueType &value) {
|
||||
data.reserve(data.size() + sizeof(ValueType));
|
||||
auto item{reinterpret_cast<const u8 *>(&value)};
|
||||
for (size_t index{}; sizeof(ValueType) > index; index++) {
|
||||
data.push_back(*item);
|
||||
item++;
|
||||
auto offset{data.size()};
|
||||
data.resize(offset + sizeof(ValueType));
|
||||
*(reinterpret_cast<ValueType*>(data.data() + offset)) = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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>
|
||||
void PushObject(const ObjectType &object) {
|
||||
objects.reserve(objects.size() + sizeof(ObjectType));
|
||||
auto item{reinterpret_cast<const u8 *>(&object)};
|
||||
for (size_t index{}; sizeof(ObjectType) > index; index++) {
|
||||
objects.push_back(*object);
|
||||
item++;
|
||||
}
|
||||
auto offset{objects.size()};
|
||||
objects.resize(offset + sizeof(ObjectType));
|
||||
*(reinterpret_cast<ObjectType*>(objects.data() + offset)) = object;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,9 +1,12 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// 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 <gpu.h>
|
||||
#include <gpu/format.h>
|
||||
#include <soc.h>
|
||||
#include <common/settings.h>
|
||||
#include <services/nvdrv/driver.h>
|
||||
#include <services/nvdrv/devices/nvmap.h>
|
||||
@ -11,170 +14,449 @@
|
||||
#include "GraphicBufferProducer.h"
|
||||
|
||||
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) {}
|
||||
|
||||
void GraphicBufferProducer::RequestBuffer(Parcel &in, Parcel &out) {
|
||||
u32 slot{in.Pop<u32>()};
|
||||
|
||||
out.Push<u32>(1);
|
||||
out.Push<u32>(sizeof(GbpBuffer));
|
||||
out.Push<u32>(0);
|
||||
out.Push(queue.at(slot)->gbpBuffer);
|
||||
|
||||
state.logger->Debug("#{}", slot, sizeof(GbpBuffer));
|
||||
u8 GraphicBufferProducer::GetPendingBufferCount() {
|
||||
u8 count{};
|
||||
for (auto it{queue.begin()}, end{it + activeSlotCount}; it < end; it++)
|
||||
if (it->state == BufferState::Queued)
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
void GraphicBufferProducer::DequeueBuffer(Parcel &in, Parcel &out) {
|
||||
in.Pop<u32>(); //!< async boolean flag
|
||||
u32 width{in.Pop<u32>()};
|
||||
u32 height{in.Pop<u32>()};
|
||||
u32 format{in.Pop<u32>()};
|
||||
u32 usage{in.Pop<u32>()};
|
||||
|
||||
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);
|
||||
AndroidStatus GraphicBufferProducer::RequestBuffer(i32 slot, GraphicBuffer *&buffer) {
|
||||
std::lock_guard guard(mutex);
|
||||
if (slot < 0 || slot >= queue.size()) [[unlikely]] {
|
||||
state.logger->Warn("#{} was out of range", slot);
|
||||
return AndroidStatus::BadValue;
|
||||
}
|
||||
|
||||
out.Push(slot);
|
||||
out.Push(std::array<u32, 13>{1, 0x24}); // Unknown
|
||||
auto &bufferSlot{queue[slot]};
|
||||
bufferSlot.wasBufferRequested = true;
|
||||
buffer = bufferSlot.graphicBuffer.get();
|
||||
|
||||
state.logger->Debug("#{} - Dimensions: {}x{}, Format: {}, Usage: 0x{:X}", slot, width, height, format, usage);
|
||||
state.logger->Debug("#{}", slot);
|
||||
return AndroidStatus::Ok;
|
||||
}
|
||||
|
||||
void GraphicBufferProducer::QueueBuffer(Parcel &in, Parcel &out) {
|
||||
struct Data {
|
||||
u32 slot;
|
||||
u64 timestamp;
|
||||
u32 autoTimestamp;
|
||||
ARect crop;
|
||||
u32 scalingMode;
|
||||
u32 transform;
|
||||
u32 stickyTransform;
|
||||
u64 _unk0_;
|
||||
u32 swapInterval;
|
||||
std::array<nvdrv::Fence, 4> fence;
|
||||
} &data{in.Pop<Data>()};
|
||||
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;
|
||||
}
|
||||
|
||||
auto buffer{queue.at(data.slot)};
|
||||
buffer->texture->SynchronizeHost();
|
||||
state.gpu->presentation.Present(buffer->texture);
|
||||
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;
|
||||
|
||||
auto &buffer{queue.at(slot)};
|
||||
if (!buffer.graphicBuffer) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
buffer.state = BufferState::Dequeued;
|
||||
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}, Is Async: {}", slot, width, height, ToString(format), usage, async);
|
||||
return AndroidStatus::Ok;
|
||||
}
|
||||
|
||||
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) {
|
||||
switch (scalingMode) {
|
||||
case NativeWindowScalingMode::Freeze:
|
||||
case NativeWindowScalingMode::ScaleToWindow:
|
||||
case NativeWindowScalingMode::ScaleCrop:
|
||||
case NativeWindowScalingMode::NoScaleCrop:
|
||||
break;
|
||||
|
||||
default:
|
||||
state.logger->Warn("{} is not a valid scaling mode", static_cast<u32>(scalingMode));
|
||||
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();
|
||||
|
||||
struct {
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 _pad_[3];
|
||||
} output{
|
||||
.width = buffer->gbpBuffer.width,
|
||||
.height = buffer->gbpBuffer.height,
|
||||
};
|
||||
out.Push(output);
|
||||
width = defaultWidth;
|
||||
height = defaultHeight;
|
||||
transformHint = state.gpu->presentation.GetTransformHint();
|
||||
pendingBufferCount = GetPendingBufferCount();
|
||||
|
||||
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) {
|
||||
u32 slot{in.Pop<u32>()};
|
||||
//auto fences{in.Pop<std::array<nvdrv::Fence, 4>>()};
|
||||
void GraphicBufferProducer::CancelBuffer(i32 slot, const AndroidFence &fence) {
|
||||
std::lock_guard guard(mutex);
|
||||
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
|
||||
// As a result of this, we just assume it'll be presented and dequeued at some point and not cancel the buffer here
|
||||
auto &buffer{queue[slot]};
|
||||
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);
|
||||
}
|
||||
|
||||
void GraphicBufferProducer::Connect(Parcel &out) {
|
||||
struct {
|
||||
u32 width{constant::HandheldResolutionW}; //!< The width of the display
|
||||
u32 height{constant::HandheldResolutionH}; //!< The height of the display
|
||||
u32 transformHint{}; //!< A hint of the transformation of the display
|
||||
u32 pendingBuffers{}; //!< The number of pending buffers
|
||||
u32 status{}; //!< The status of the buffer queue
|
||||
} data{};
|
||||
out.Push(data);
|
||||
state.logger->Debug("{}x{}", data.width, data.height);
|
||||
AndroidStatus GraphicBufferProducer::Query(NativeWindowQuery query, u32 &out) {
|
||||
std::lock_guard guard(mutex);
|
||||
switch (query) {
|
||||
case NativeWindowQuery::Width:
|
||||
out = defaultWidth;
|
||||
break;
|
||||
|
||||
case NativeWindowQuery::Height:
|
||||
out = defaultHeight;
|
||||
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;
|
||||
}
|
||||
|
||||
default:
|
||||
state.logger->Warn("Query not supported: {}", static_cast<u32>(query));
|
||||
return AndroidStatus::BadValue;
|
||||
}
|
||||
|
||||
state.logger->Debug("{}: {}", ToString(query), out);
|
||||
return AndroidStatus::Ok;
|
||||
}
|
||||
|
||||
void GraphicBufferProducer::SetPreallocatedBuffer(Parcel &in) {
|
||||
struct Data {
|
||||
u32 slot;
|
||||
u32 _unk0_;
|
||||
u32 length;
|
||||
u32 _pad0_;
|
||||
} &data = in.Pop<Data>();
|
||||
|
||||
auto &gbpBuffer{in.Pop<GbpBuffer>()};
|
||||
|
||||
std::shared_ptr<nvdrv::device::NvMap::NvMapObject> nvBuffer{};
|
||||
|
||||
auto driver{nvdrv::driver.lock()};
|
||||
auto nvmap{driver->nvMap.lock()};
|
||||
|
||||
if (gbpBuffer.nvmapHandle) {
|
||||
nvBuffer = nvmap->GetObject(gbpBuffer.nvmapHandle);
|
||||
} else {
|
||||
std::shared_lock nvmapLock(nvmap->mapMutex);
|
||||
for (const auto &object : nvmap->maps) {
|
||||
if (object->id == gbpBuffer.nvmapId) {
|
||||
nvBuffer = object;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!nvBuffer)
|
||||
throw exception("A QueueBuffer request has an invalid NVMap Handle ({}) and ID ({})", gbpBuffer.nvmapHandle, gbpBuffer.nvmapId);
|
||||
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 (gbpBuffer.format) {
|
||||
case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
|
||||
case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
|
||||
switch (graphicBuffer.format) {
|
||||
case AndroidPixelFormat::RGBA8888:
|
||||
case AndroidPixelFormat::RGBX8888:
|
||||
format = gpu::format::RGBA8888Unorm;
|
||||
break;
|
||||
case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
|
||||
|
||||
case AndroidPixelFormat::RGB565:
|
||||
format = gpu::format::RGB565Unorm;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw exception("Unknown pixel format used for FB");
|
||||
throw exception("Unknown format in buffer: '{}' ({})", ToString(graphicBuffer.format), static_cast<u32>(graphicBuffer.format));
|
||||
}
|
||||
|
||||
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 &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{};
|
||||
{
|
||||
auto driver{nvdrv::driver.lock()};
|
||||
auto nvmap{driver->nvMap.lock()};
|
||||
if (surface.nvmapHandle) {
|
||||
nvBuffer = nvmap->GetObject(surface.nvmapHandle);
|
||||
} else {
|
||||
std::shared_lock nvmapLock(nvmap->mapMutex);
|
||||
for (const auto &object : nvmap->maps) {
|
||||
if (object->id == handle.nvmapId) {
|
||||
nvBuffer = object;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!nvBuffer)
|
||||
throw exception("A QueueBuffer request has an invalid NvMap Handle ({}) and ID ({})", surface.nvmapHandle, handle.nvmapId);
|
||||
}
|
||||
}
|
||||
|
||||
if (surface.size > (nvBuffer->size - surface.offset))
|
||||
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);
|
||||
|
||||
gpu::texture::TileMode tileMode;
|
||||
gpu::texture::TileConfig tileConfig;
|
||||
if (surface.layout != NvSurfaceLayout::Blocklinear) {
|
||||
tileMode = gpu::texture::TileMode::Block;
|
||||
tileConfig = {
|
||||
.surfaceWidth = static_cast<u16>(surface.width),
|
||||
.blockHeight = static_cast<u8>(1U << surface.blockHeightLog2),
|
||||
.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 + 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.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) {
|
||||
switch (code) {
|
||||
case TransactionCode::RequestBuffer:
|
||||
RequestBuffer(in, out);
|
||||
case TransactionCode::RequestBuffer: {
|
||||
GraphicBuffer *buffer{};
|
||||
auto result{RequestBuffer(in.Pop<i32>(), buffer)};
|
||||
out.PushOptionalFlattenable(buffer);
|
||||
out.Push(result);
|
||||
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;
|
||||
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;
|
||||
case TransactionCode::CancelBuffer:
|
||||
CancelBuffer(in);
|
||||
}
|
||||
|
||||
case TransactionCode::CancelBuffer: {
|
||||
CancelBuffer(in.Pop<i32>(), in.Pop<AndroidFence>());
|
||||
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;
|
||||
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;
|
||||
case TransactionCode::Disconnect:
|
||||
}
|
||||
|
||||
case TransactionCode::Disconnect: {
|
||||
auto result{Disconnect(in.Pop<NativeWindowApi>())};
|
||||
out.Push(result);
|
||||
break;
|
||||
case TransactionCode::SetPreallocatedBuffer:
|
||||
SetPreallocatedBuffer(in);
|
||||
}
|
||||
|
||||
case TransactionCode::SetPreallocatedBuffer: {
|
||||
SetPreallocatedBuffer(in.Pop<i32>(), *in.PopOptionalFlattenable<GraphicBuffer>());
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw exception("An unimplemented transaction was called: {}", static_cast<u32>(code));
|
||||
}
|
||||
|
@ -1,49 +1,55 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// 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
|
||||
|
||||
#include <services/common/parcel.h>
|
||||
#include "android_types.h"
|
||||
#include "native_window.h"
|
||||
|
||||
namespace skyline::gpu {
|
||||
class Texture;
|
||||
}
|
||||
|
||||
namespace skyline::service::hosbinder {
|
||||
/**
|
||||
* @brief A descriptor for the surfaceflinger graphics buffer
|
||||
* @url https://github.com/reswitched/libtransistor/blob/0f0c36227842c344d163922fc98ee76229e9f0ee/lib/display/graphic_buffer_queue.c#L66
|
||||
*/
|
||||
struct GbpBuffer {
|
||||
u32 magic; //!< The magic of the graphics buffer: 0x47424652
|
||||
u32 width; //!< The width of the buffer
|
||||
u32 height; //!< The height of the buffer
|
||||
u32 stride; //!< The stride of the buffer
|
||||
u32 format; //!< The format of the buffer, this corresponds to AHardwareBuffer_Format
|
||||
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];
|
||||
#define ENUM_CASE(key) \
|
||||
case ENUM_TYPE::key: \
|
||||
return #key
|
||||
|
||||
#define ENUM_STRING(name, cases) \
|
||||
constexpr const char *ToString(name value) { \
|
||||
using ENUM_TYPE = name; \
|
||||
switch (value) { \
|
||||
cases \
|
||||
default: \
|
||||
return "Unknown"; \
|
||||
}; \
|
||||
};
|
||||
|
||||
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 {
|
||||
public:
|
||||
std::shared_ptr<gpu::Texture> texture;
|
||||
GbpBuffer gbpBuffer;
|
||||
enum class BufferState {
|
||||
Free,
|
||||
Dequeued,
|
||||
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
|
||||
* @url https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp
|
||||
* @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://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 {
|
||||
private:
|
||||
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:
|
||||
DisplayId displayId{DisplayId::Null}; //!< The ID of this display
|
||||
LayerStatus layerStatus{LayerStatus::Uninitialized}; //!< The status of the single layer the display has
|
||||
|
||||
/**
|
||||
* @brief The functions called by TransactParcel for android.gui.IGraphicBufferProducer
|
||||
* @refitem https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#35
|
||||
* @brief The transactions supported by android.gui.IGraphicBufferProducer
|
||||
* @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 {
|
||||
RequestBuffer = 1, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#281
|
||||
SetBufferCount = 2, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#293
|
||||
DequeueBuffer = 3, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#300
|
||||
DetachBuffer = 4, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#318
|
||||
DetachNextBuffer = 5, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#325
|
||||
AttachBuffer = 6, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#343
|
||||
QueueBuffer = 7, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#353
|
||||
CancelBuffer = 8, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#364
|
||||
Query = 9, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#372
|
||||
Connect = 10, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#381
|
||||
Disconnect = 11, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#396
|
||||
SetSidebandStream = 12, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#403
|
||||
AllocateBuffers = 13, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#413
|
||||
SetPreallocatedBuffer = 14, //!< No source on this but it's used to set a existing buffer according to libtransistor and libnx
|
||||
RequestBuffer = 1,
|
||||
SetBufferCount = 2,
|
||||
DequeueBuffer = 3,
|
||||
DetachBuffer = 4,
|
||||
DetachNextBuffer = 5,
|
||||
AttachBuffer = 6,
|
||||
QueueBuffer = 7,
|
||||
CancelBuffer = 8,
|
||||
Query = 9,
|
||||
Connect = 10,
|
||||
Disconnect = 11,
|
||||
SetSidebandStream = 12,
|
||||
AllocateBuffers = 13,
|
||||
SetPreallocatedBuffer = 14, //!< A transaction specific to HOS, see the implementation for a description of its functionality
|
||||
};
|
||||
|
||||
GraphicBufferProducer(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
#undef ENUM_CASE
|
||||
|
@ -36,11 +36,11 @@ namespace skyline::service::hosbinder {
|
||||
*/
|
||||
Result GetNativeHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
SERVICE_DECL(
|
||||
SFUNC(0x0, IHOSBinderDriver, TransactParcel),
|
||||
SFUNC(0x1, IHOSBinderDriver, AdjustRefcount),
|
||||
SFUNC(0x2, IHOSBinderDriver, GetNativeHandle),
|
||||
SFUNC(0x3, IHOSBinderDriver, TransactParcel)
|
||||
)
|
||||
SERVICE_DECL(
|
||||
SFUNC(0x0, IHOSBinderDriver, TransactParcel),
|
||||
SFUNC(0x1, IHOSBinderDriver, AdjustRefcount),
|
||||
SFUNC(0x2, IHOSBinderDriver, GetNativeHandle),
|
||||
SFUNC(0x3, IHOSBinderDriver, TransactParcel)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
234
app/src/main/cpp/skyline/services/hosbinder/android_types.h
Normal file
234
app/src/main/cpp/skyline/services/hosbinder/android_types.h
Normal 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
|
127
app/src/main/cpp/skyline/services/hosbinder/native_window.h
Normal file
127
app/src/main/cpp/skyline/services/hosbinder/native_window.h
Normal 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
|
@ -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/)
|
||||
|
||||
#pragma once
|
||||
|
@ -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/)
|
||||
|
||||
#pragma once
|
||||
|
@ -18,7 +18,7 @@ namespace skyline::soc::gm20b::engine {
|
||||
/**
|
||||
* @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 {
|
||||
std::array<u32, RegisterCount> raw;
|
||||
|
||||
@ -162,7 +162,7 @@ namespace skyline::soc::gm20b::engine {
|
||||
};
|
||||
} registers{};
|
||||
static_assert(sizeof(Registers) == (RegisterCount * sizeof(u32)));
|
||||
#pragma pack(pop)
|
||||
#pragma pack(pop)
|
||||
|
||||
public:
|
||||
GPFIFO(const DeviceState &state) : Engine(state) {}
|
||||
|
@ -13,7 +13,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
||||
*/
|
||||
class MacroInterpreter {
|
||||
private:
|
||||
#pragma pack(push, 1)
|
||||
#pragma pack(push, 1)
|
||||
union Opcode {
|
||||
u32 raw;
|
||||
|
||||
@ -89,8 +89,8 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
||||
}
|
||||
} bitfield;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
static_assert(sizeof(Opcode) == sizeof(u32));
|
||||
#pragma pack(pop)
|
||||
|
||||
/**
|
||||
* @brief Metadata about the Maxwell 3D method to be called in 'Send'
|
||||
|
@ -33,7 +33,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
||||
/**
|
||||
* @url https://github.com/devkitPro/deko3d/blob/master/source/maxwell/engine_3d.def#L478
|
||||
*/
|
||||
#pragma pack(push, 1)
|
||||
#pragma pack(push, 1)
|
||||
union Registers {
|
||||
std::array<u32, RegisterCount> raw;
|
||||
|
||||
@ -552,7 +552,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(Registers) == (RegisterCount * sizeof(u32)));
|
||||
#pragma pack(pop)
|
||||
#pragma pack(pop)
|
||||
|
||||
Registers registers{};
|
||||
Registers shadowRegisters{}; //!< The shadow registers, their function is controlled by the 'shadowRamControl' register
|
||||
|
@ -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/)
|
||||
|
||||
#pragma once
|
||||
|
@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
// Copyright © 2020 Ryujinx Team and Contributors
|
||||
|
||||
#include "syncpoint.h"
|
||||
|
||||
@ -38,13 +39,15 @@ namespace skyline::soc::host1x {
|
||||
}
|
||||
|
||||
bool Syncpoint::Wait(u32 threshold, std::chrono::steady_clock::duration timeout) {
|
||||
if (value >= threshold)
|
||||
return true;
|
||||
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
bool flag{};
|
||||
|
||||
if (timeout == std::chrono::steady_clock::duration::max())
|
||||
timeout = std::chrono::seconds(1);
|
||||
|
||||
if (!RegisterWaiter(threshold, [&cv, &mtx, &flag] {
|
||||
std::unique_lock lock(mtx);
|
||||
flag = true;
|
||||
|
@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
// Copyright © 2020 Ryujinx Team and Contributors
|
||||
|
||||
#pragma once
|
||||
|
||||
@ -45,7 +46,7 @@ namespace skyline::soc::host1x {
|
||||
|
||||
/**
|
||||
* @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);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user