diff --git a/app/src/main/cpp/skyline/common.h b/app/src/main/cpp/skyline/common.h index 2ac4c151..8f00538b 100644 --- a/app/src/main/cpp/skyline/common.h +++ b/app/src/main/cpp/skyline/common.h @@ -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 - 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 - Return ValuePointer(T item) { + constexpr Return ValuePointer(T item) { if constexpr (std::is_pointer::value) return reinterpret_cast(item); else diff --git a/app/src/main/cpp/skyline/gpu.cpp b/app/src/main/cpp/skyline/gpu.cpp index 53135f1a..931bd59b 100644 --- a/app/src/main/cpp/skyline/gpu.cpp +++ b/app/src/main/cpp/skyline/gpu.cpp @@ -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 diff --git a/app/src/main/cpp/skyline/gpu/presentation_engine.cpp b/app/src/main/cpp/skyline/gpu/presentation_engine.cpp index af6fbcfb..f326648a 100644 --- a/app/src/main/cpp/skyline/gpu/presentation_engine.cpp +++ b/app/src/main/cpp/skyline/gpu/presentation_engine.cpp @@ -3,7 +3,7 @@ #include #include -#include "jvm.h" +#include #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 PresentationEngine::CreatePresentationTexture(const std::shared_ptr &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::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::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::max()); + + slot = nextImage.second; + return AndroidStatus::Ok; } - return nextImage.second; + return AndroidStatus::Busy; } - void PresentationEngine::Present(const std::shared_ptr &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; + } } diff --git a/app/src/main/cpp/skyline/gpu/presentation_engine.h b/app/src/main/cpp/skyline/gpu/presentation_engine.h index a20b46a1..3aae3700 100644 --- a/app/src/main/cpp/skyline/gpu/presentation_engine.h +++ b/app/src/main/cpp/skyline/gpu/presentation_engine.h @@ -5,6 +5,8 @@ #include #include +#include +#include #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 vkSurface; //!< The Vulkan Surface object that is backed by ANativeWindow + std::optional transformHint; //!< The optimal transform for the application to render as std::optional 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 CreatePresentationTexture(const std::shared_ptr &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); + void Present(i32 slot); + + /** + * @return A transform that the application should render with to elide costly transforms later + */ + service::hosbinder::NativeWindowTransform GetTransformHint(); }; } diff --git a/app/src/main/cpp/skyline/gpu/texture.h b/app/src/main/cpp/skyline/gpu/texture.h index af2d2ae0..cae7ae1d 100644 --- a/app/src/main/cpp/skyline/gpu/texture.h +++ b/app/src/main/cpp/skyline/gpu/texture.h @@ -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 }; /** diff --git a/app/src/main/cpp/skyline/kernel/types/KSharedMemory.cpp b/app/src/main/cpp/skyline/kernel/types/KSharedMemory.cpp index 0f6c63df..f254016f 100644 --- a/app/src/main/cpp/skyline/kernel/types/KSharedMemory.cpp +++ b/app/src/main/cpp/skyline/kernel/types/KSharedMemory.cpp @@ -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, diff --git a/app/src/main/cpp/skyline/services/am/controller/ICommonStateGetter.cpp b/app/src/main/cpp/skyline/services/am/controller/ICommonStateGetter.cpp index d98ed566..2a74c035 100644 --- a/app/src/main/cpp/skyline/services/am/controller/ICommonStateGetter.cpp +++ b/app/src/main/cpp/skyline/services/am/controller/ICommonStateGetter.cpp @@ -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(constant::HandheldResolutionW); - response.Push(constant::HandheldResolutionH); + constexpr u16 HandheldResolutionW{1280}; + constexpr u16 HandheldResolutionH{720}; + response.Push(HandheldResolutionW); + response.Push(HandheldResolutionH); } else if (operationMode == OperationMode::Docked) { - response.Push(constant::DockedResolutionW); - response.Push(constant::DockedResolutionH); + constexpr u16 DockedResolutionW{1920}; + constexpr u16 DockedResolutionH{1080}; + response.Push(DockedResolutionW); + response.Push(DockedResolutionH); } return {}; } diff --git a/app/src/main/cpp/skyline/services/common/parcel.h b/app/src/main/cpp/skyline/services/common/parcel.h index faac6805..a6615100 100644 --- a/app/src/main/cpp/skyline/services/common/parcel.h +++ b/app/src/main/cpp/skyline/services/common/parcel.h @@ -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 + ValueType* PopOptionalFlattenable() { + bool hasObject{static_cast(Pop())}; + if (hasObject) { + auto size{Pop()}; + if (size != sizeof(ValueType)) + throw exception("Popping flattenable of size 0x{:X} with type size 0x{:X}", size, sizeof(ValueType)); + return &Pop(); + } else { + return nullptr; + } + } + /** * @brief Writes a value to the Parcel */ template void Push(const ValueType &value) { - data.reserve(data.size() + sizeof(ValueType)); - auto item{reinterpret_cast(&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(data.data() + offset)) = value; + } + + /** + * @brief Writes a 32-bit boolean flag denoting if an object exists alongside the object if it exists + */ + template + void PushOptionalFlattenable(ObjectType *pointer) { + Push(pointer != nullptr); + if (pointer) { + Push(sizeof(ObjectType)); // Object Size + Push(0); // FD Count + Push(*pointer); + } + } + + template + void PushOptionalFlattenable(std::optional object) { + Push(object.has_value()); + if (object) { + Push(sizeof(ObjectType)); + Push(0); + Push(*object); } } @@ -67,12 +103,9 @@ namespace skyline::service { */ template void PushObject(const ObjectType &object) { - objects.reserve(objects.size() + sizeof(ObjectType)); - auto item{reinterpret_cast(&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(objects.data() + offset)) = object; } /** diff --git a/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.cpp b/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.cpp index ee1e18b5..a28b9624 100644 --- a/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.cpp +++ b/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.cpp @@ -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 #include #include +#include #include #include #include @@ -11,170 +14,449 @@ #include "GraphicBufferProducer.h" namespace skyline::service::hosbinder { - Buffer::Buffer(const GbpBuffer &gbpBuffer, std::shared_ptr 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()}; - - out.Push(1); - out.Push(sizeof(GbpBuffer)); - out.Push(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(); //!< async boolean flag - u32 width{in.Pop()}; - u32 height{in.Pop()}; - u32 format{in.Pop()}; - u32 usage{in.Pop()}; - - 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{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 fence; - } &data{in.Pop()}; + AndroidStatus GraphicBufferProducer::DequeueBuffer(bool async, u32 width, u32 height, AndroidPixelFormat format, u32 usage, i32 &slot, std::optional &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(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(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(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()}; - //auto fences{in.Pop>()}; + 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(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(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(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(); - - auto &gbpBuffer{in.Pop()}; - - std::shared_ptr 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(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(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::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(graphicBuffer.format)); } - auto texture{std::make_shared(state, nvBuffer->ptr + gbpBuffer.offset, gpu::texture::Dimensions(gbpBuffer.width, gbpBuffer.height), format, gpu::texture::TileMode::Block, gpu::texture::TileConfig{.surfaceWidth = static_cast(gbpBuffer.stride), .blockHeight = static_cast(1U << gbpBuffer.blockHeightLog2), .blockDepth = 1})}; + 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 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(surface.width), + .blockHeight = static_cast(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(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); + buffer.texture = state.gpu->presentation.CreatePresentationTexture(texture, slot); + + activeSlotCount = hasBufferCount = std::count_if(queue.begin(), queue.end(), [](const BufferSlot &slot) { return static_cast(slot.graphicBuffer); }); - queue.resize(std::max(data.slot + 1, static_cast(queue.size()))); - queue[data.slot] = std::make_shared(gbpBuffer, state.gpu->presentation.CreatePresentationTexture(texture, data.slot)); state.gpu->presentation.bufferEvent->Signal(); - state.logger->Debug("#{} - 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(), buffer)}; + out.PushOptionalFlattenable(buffer); + out.Push(result); break; - case TransactionCode::DequeueBuffer: - DequeueBuffer(in, out); + } + + case TransactionCode::DequeueBuffer: { + i32 slot{}; + std::optional fence{}; + auto result{DequeueBuffer(in.Pop(), in.Pop(), in.Pop(), in.Pop(), in.Pop(), 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()}; + auto queueBufferInputSize{in.Pop()}; + 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(), in.Pop(), in.Pop(), in.Pop(), in.Pop(), in.Pop(), in.Pop(), in.Pop(), in.Pop(), 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(), in.Pop()); break; - case TransactionCode::Query: - out.Push(0); + } + + case TransactionCode::Query: { + u32 queryOut{}; + auto result{Query(in.Pop(), queryOut)}; + out.Push(queryOut); + out.Push(result); break; - case TransactionCode::Connect: - Connect(out); + } + + case TransactionCode::Connect: { + auto hasProducerListener{static_cast(in.Pop())}; + if (hasProducerListener) + throw exception("Callbacks using IProducerListener are not supported"); + + u32 width{}, height{}, pendingBufferCount{}; + NativeWindowTransform transformHint{}; + auto result{Connect(in.Pop(), in.Pop(), 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())}; + out.Push(result); break; - case TransactionCode::SetPreallocatedBuffer: - SetPreallocatedBuffer(in); + } + + case TransactionCode::SetPreallocatedBuffer: { + SetPreallocatedBuffer(in.Pop(), *in.PopOptionalFlattenable()); break; + } + default: throw exception("An unimplemented transaction was called: {}", static_cast(code)); } diff --git a/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.h b/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.h index ef6436f7..9d27295d 100644 --- a/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.h +++ b/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.h @@ -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 +#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 texture; - GbpBuffer gbpBuffer; + enum class BufferState { + Free, + Dequeued, + Queued, + Acquired, + }; - Buffer(const GbpBuffer &gbpBuffer, std::shared_ptr 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 texture{}; + std::unique_ptr 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> 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 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 &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 producer; //!< A globally shared instance of the GraphicsBufferProducer } + +#undef ENUM_CASE diff --git a/app/src/main/cpp/skyline/services/hosbinder/IHOSBinderDriver.h b/app/src/main/cpp/skyline/services/hosbinder/IHOSBinderDriver.h index 5a3eca4c..b28ef62b 100644 --- a/app/src/main/cpp/skyline/services/hosbinder/IHOSBinderDriver.h +++ b/app/src/main/cpp/skyline/services/hosbinder/IHOSBinderDriver.h @@ -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) + ) }; } diff --git a/app/src/main/cpp/skyline/services/hosbinder/android_types.h b/app/src/main/cpp/skyline/services/hosbinder/android_types.h new file mode 100644 index 00000000..f146e8e3 --- /dev/null +++ b/app/src/main/cpp/skyline/services/hosbinder/android_types.h @@ -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 +#include +#include + +#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 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::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 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 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 diff --git a/app/src/main/cpp/skyline/services/hosbinder/native_window.h b/app/src/main/cpp/skyline/services/hosbinder/native_window.h new file mode 100644 index 00000000..29a00960 --- /dev/null +++ b/app/src/main/cpp/skyline/services/hosbinder/native_window.h @@ -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 diff --git a/app/src/main/cpp/skyline/soc.h b/app/src/main/cpp/skyline/soc.h index f2201cf7..a0316a23 100644 --- a/app/src/main/cpp/skyline/soc.h +++ b/app/src/main/cpp/skyline/soc.h @@ -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 diff --git a/app/src/main/cpp/skyline/soc/gm20b.h b/app/src/main/cpp/skyline/soc/gm20b.h index 15389e99..a5b7a9bb 100644 --- a/app/src/main/cpp/skyline/soc/gm20b.h +++ b/app/src/main/cpp/skyline/soc/gm20b.h @@ -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 diff --git a/app/src/main/cpp/skyline/soc/gm20b/engines/gpfifo.h b/app/src/main/cpp/skyline/soc/gm20b/engines/gpfifo.h index f78cf8ff..a4fbde79 100644 --- a/app/src/main/cpp/skyline/soc/gm20b/engines/gpfifo.h +++ b/app/src/main/cpp/skyline/soc/gm20b/engines/gpfifo.h @@ -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 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) {} diff --git a/app/src/main/cpp/skyline/soc/gm20b/engines/maxwell/macro_interpreter.h b/app/src/main/cpp/skyline/soc/gm20b/engines/maxwell/macro_interpreter.h index 981d76f3..c7732931 100644 --- a/app/src/main/cpp/skyline/soc/gm20b/engines/maxwell/macro_interpreter.h +++ b/app/src/main/cpp/skyline/soc/gm20b/engines/maxwell/macro_interpreter.h @@ -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' diff --git a/app/src/main/cpp/skyline/soc/gm20b/engines/maxwell_3d.h b/app/src/main/cpp/skyline/soc/gm20b/engines/maxwell_3d.h index fba18554..32eb8a75 100644 --- a/app/src/main/cpp/skyline/soc/gm20b/engines/maxwell_3d.h +++ b/app/src/main/cpp/skyline/soc/gm20b/engines/maxwell_3d.h @@ -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 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 diff --git a/app/src/main/cpp/skyline/soc/host1x.h b/app/src/main/cpp/skyline/soc/host1x.h index 006c0a2c..ac3a04a8 100644 --- a/app/src/main/cpp/skyline/soc/host1x.h +++ b/app/src/main/cpp/skyline/soc/host1x.h @@ -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 diff --git a/app/src/main/cpp/skyline/soc/host1x/syncpoint.cpp b/app/src/main/cpp/skyline/soc/host1x/syncpoint.cpp index 39ba28c7..9f08bd83 100644 --- a/app/src/main/cpp/skyline/soc/host1x/syncpoint.cpp +++ b/app/src/main/cpp/skyline/soc/host1x/syncpoint.cpp @@ -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; diff --git a/app/src/main/cpp/skyline/soc/host1x/syncpoint.h b/app/src/main/cpp/skyline/soc/host1x/syncpoint.h index 1a0a73c0..716b8aff 100644 --- a/app/src/main/cpp/skyline/soc/host1x/syncpoint.h +++ b/app/src/main/cpp/skyline/soc/host1x/syncpoint.h @@ -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); };