Support Swap Interval + Cropping + Transforms and more for QueueBuffer

Support for the following parameters was added to `QueueBuffer`:
* Earliest Present Timestamp
* Swap Interval
* Crop
* Scaling Mode
* Transform
* Frame ID (Not returned to guest yet)
We utilize ANativeWindow APIs directly to achieve all of this in an efficient manner since HWC will be used directly for it, we do plan to introduce Vulkan equivalents for all of these operations later down the line for a port to non-Android platforms.
This commit is contained in:
PixelyIon 2021-06-25 07:03:02 +05:30
parent 8d4a8564f5
commit 05bd7faeb6
7 changed files with 444 additions and 28 deletions

View File

@ -371,7 +371,7 @@ namespace skyline {
* @param nullTerminated If true and the string is null-terminated, a view of it will be returned (not including the null terminator itself), otherwise the entire span will be returned as a string view * @param nullTerminated If true and the string is null-terminated, a view of it will be returned (not including the null terminator itself), otherwise the entire span will be returned as a string view
*/ */
constexpr std::string_view as_string(bool nullTerminated = false) { constexpr std::string_view as_string(bool nullTerminated = false) {
return std::string_view(reinterpret_cast<char *>(span::data()), nullTerminated ? (std::find(span::begin(), span::end(), 0) - span::begin()) : span::size_bytes()); return std::string_view(reinterpret_cast<const char *>(span::data()), nullTerminated ? (std::find(span::begin(), span::end(), 0) - span::begin()) : span::size_bytes());
} }
template<typename Out, size_t OutExtent = std::dynamic_extent> template<typename Out, size_t OutExtent = std::dynamic_extent>

View File

@ -0,0 +1,303 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
// Copyright © 2021 The Android Open Source Project
#pragma once
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativebase/include/nativebase/nativebase.h;l=29;drc=cb496acbe593326e8d5d563847067d02b2df40ec
*/
#define ANDROID_NATIVE_UNSIGNED_CAST(x) static_cast<unsigned int>(x)
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativebase/include/nativebase/nativebase.h;l=34-38;drc=cb496acbe593326e8d5d563847067d02b2df40ec
*/
#define ANDROID_NATIVE_MAKE_CONSTANT(a, b, c, d) \
((ANDROID_NATIVE_UNSIGNED_CAST(a) << 24) | \
(ANDROID_NATIVE_UNSIGNED_CAST(b) << 16) | \
(ANDROID_NATIVE_UNSIGNED_CAST(c) << 8) | \
(ANDROID_NATIVE_UNSIGNED_CAST(d)))
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativewindow/include/system/window.h;l=60;drc=401cda638e7d17f6697b5a65c9a5ad79d056202d
*/
#define ANDROID_NATIVE_WINDOW_MAGIC ANDROID_NATIVE_MAKE_CONSTANT('_','w','n','d')
constexpr int AndroidNativeWindowMagic{ANDROID_NATIVE_WINDOW_MAGIC};
#undef ANDROID_NATIVE_WINDOW_MAGIC
#undef ANDROID_NATIVE_MAKE_CONSTANT
#undef ANDROID_NATIVE_UNSIGNED_CAST
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativewindow/include/system/window.h;l=325-331;drc=401cda638e7d17f6697b5a65c9a5ad79d056202d
*/
constexpr int64_t NativeWindowTimestampAuto{-9223372036854775807LL - 1};
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativewindow/include/system/window.h;l=198-259;drc=401cda638e7d17f6697b5a65c9a5ad79d056202d
*/
enum {
NATIVE_WINDOW_CONNECT = 1, /* deprecated */
NATIVE_WINDOW_DISCONNECT = 2, /* deprecated */
NATIVE_WINDOW_SET_CROP = 3, /* private */
NATIVE_WINDOW_SET_BUFFER_COUNT = 4,
NATIVE_WINDOW_SET_BUFFERS_TRANSFORM = 6,
NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP = 7,
NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS = 8,
NATIVE_WINDOW_SET_SCALING_MODE = 10, /* private */
NATIVE_WINDOW_LOCK = 11, /* private */
NATIVE_WINDOW_UNLOCK_AND_POST = 12, /* private */
NATIVE_WINDOW_API_CONNECT = 13, /* private */
NATIVE_WINDOW_API_DISCONNECT = 14, /* private */
NATIVE_WINDOW_SET_BUFFERS_USER_DIMENSIONS = 15, /* private */
NATIVE_WINDOW_SET_POST_TRANSFORM_CROP = 16, /* deprecated, unimplemented */
NATIVE_WINDOW_SET_BUFFERS_STICKY_TRANSFORM = 17, /* private */
NATIVE_WINDOW_SET_SIDEBAND_STREAM = 18,
NATIVE_WINDOW_SET_BUFFERS_DATASPACE = 19,
NATIVE_WINDOW_SET_SURFACE_DAMAGE = 20, /* private */
NATIVE_WINDOW_SET_SHARED_BUFFER_MODE = 21,
NATIVE_WINDOW_SET_AUTO_REFRESH = 22,
NATIVE_WINDOW_GET_REFRESH_CYCLE_DURATION = 23,
NATIVE_WINDOW_GET_NEXT_FRAME_ID = 24,
NATIVE_WINDOW_ENABLE_FRAME_TIMESTAMPS = 25,
NATIVE_WINDOW_GET_COMPOSITOR_TIMING = 26,
NATIVE_WINDOW_GET_FRAME_TIMESTAMPS = 27,
NATIVE_WINDOW_GET_WIDE_COLOR_SUPPORT = 28,
NATIVE_WINDOW_GET_HDR_SUPPORT = 29,
NATIVE_WINDOW_GET_CONSUMER_USAGE64 = 31,
NATIVE_WINDOW_SET_BUFFERS_SMPTE2086_METADATA = 32,
NATIVE_WINDOW_SET_BUFFERS_CTA861_3_METADATA = 33,
NATIVE_WINDOW_SET_BUFFERS_HDR10_PLUS_METADATA = 34,
NATIVE_WINDOW_SET_AUTO_PREROTATION = 35,
NATIVE_WINDOW_GET_LAST_DEQUEUE_START = 36, /* private */
NATIVE_WINDOW_SET_DEQUEUE_TIMEOUT = 37, /* private */
NATIVE_WINDOW_GET_LAST_DEQUEUE_DURATION = 38, /* private */
NATIVE_WINDOW_GET_LAST_QUEUE_DURATION = 39, /* private */
NATIVE_WINDOW_SET_FRAME_RATE = 40,
NATIVE_WINDOW_SET_CANCEL_INTERCEPTOR = 41, /* private */
NATIVE_WINDOW_SET_DEQUEUE_INTERCEPTOR = 42, /* private */
NATIVE_WINDOW_SET_PERFORM_INTERCEPTOR = 43, /* private */
NATIVE_WINDOW_SET_QUEUE_INTERCEPTOR = 44, /* private */
NATIVE_WINDOW_ALLOCATE_BUFFERS = 45, /* private */
NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER = 46, /* private */
NATIVE_WINDOW_SET_QUERY_INTERCEPTOR = 47, /* private */
NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER2 = 50, /* private */
};
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativebase/include/nativebase/nativebase.h;l=43-56;drc=cb496acbe593326e8d5d563847067d02b2df40ec
*/
struct android_native_base_t {
int magic;
int version;
void *reserved[4];
void (*incRef)(android_native_base_t *);
void (*decRef)(android_native_base_t *);
};
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativewindow/include/system/window.h;l=341-560;drc=401cda638e7d17f6697b5a65c9a5ad79d056202d
*/
struct ANativeWindow {
struct android_native_base_t common;
/* flags describing some attributes of this surface or its updater */
const uint32_t flags;
/* min swap interval supported by this updated */
const int minSwapInterval;
/* max swap interval supported by this updated */
const int maxSwapInterval;
/* horizontal and vertical resolution in DPI */
const float xdpi;
const float ydpi;
/* Some storage reserved for the OEM's driver. */
intptr_t oem[4];
/*
* Set the swap interval for this surface.
*
* Returns 0 on success or -errno on error.
*/
int (*setSwapInterval)(struct ANativeWindow *window,
int interval);
/*
* Hook called by EGL to acquire a buffer. After this call, the buffer
* is not locked, so its content cannot be modified. This call may block if
* no buffers are available.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* Returns 0 on success or -errno on error.
*
* XXX: This function is deprecated. It will continue to work for some
* time for binary compatibility, but the new dequeueBuffer function that
* outputs a fence file descriptor should be used in its place.
*/
int (*dequeueBuffer_DEPRECATED)(struct ANativeWindow *window,
struct ANativeWindowBuffer **buffer);
/*
* hook called by EGL to lock a buffer. This MUST be called before modifying
* the content of a buffer. The buffer must have been acquired with
* dequeueBuffer first.
*
* Returns 0 on success or -errno on error.
*
* XXX: This function is deprecated. It will continue to work for some
* time for binary compatibility, but it is essentially a no-op, and calls
* to it should be removed.
*/
int (*lockBuffer_DEPRECATED)(struct ANativeWindow *window,
struct ANativeWindowBuffer *buffer);
/*
* Hook called by EGL when modifications to the render buffer are done.
* This unlocks and post the buffer.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* Buffers MUST be queued in the same order than they were dequeued.
*
* Returns 0 on success or -errno on error.
*
* XXX: This function is deprecated. It will continue to work for some
* time for binary compatibility, but the new queueBuffer function that
* takes a fence file descriptor should be used in its place (pass a value
* of -1 for the fence file descriptor if there is no valid one to pass).
*/
int (*queueBuffer_DEPRECATED)(struct ANativeWindow *window,
struct ANativeWindowBuffer *buffer);
/*
* hook used to retrieve information about the native window.
*
* Returns 0 on success or -errno on error.
*/
int (*query)(const struct ANativeWindow *window,
int what, int *value);
/*
* hook used to perform various operations on the surface.
* (*perform)() is a generic mechanism to add functionality to
* ANativeWindow while keeping backward binary compatibility.
*
* DO NOT CALL THIS HOOK DIRECTLY. Instead, use the helper functions
* defined below.
*
* (*perform)() returns -ENOENT if the 'what' parameter is not supported
* by the surface's implementation.
*
* See above for a list of valid operations, such as
* NATIVE_WINDOW_SET_USAGE or NATIVE_WINDOW_CONNECT
*/
int (*perform)(struct ANativeWindow *window,
int operation, ...);
/*
* Hook used to cancel a buffer that has been dequeued.
* No synchronization is performed between dequeue() and cancel(), so
* either external synchronization is needed, or these functions must be
* called from the same thread.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* XXX: This function is deprecated. It will continue to work for some
* time for binary compatibility, but the new cancelBuffer function that
* takes a fence file descriptor should be used in its place (pass a value
* of -1 for the fence file descriptor if there is no valid one to pass).
*/
int (*cancelBuffer_DEPRECATED)(struct ANativeWindow *window,
struct ANativeWindowBuffer *buffer);
/*
* Hook called by EGL to acquire a buffer. This call may block if no
* buffers are available.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* The libsync fence file descriptor returned in the int pointed to by the
* fenceFd argument will refer to the fence that must signal before the
* dequeued buffer may be written to. A value of -1 indicates that the
* caller may access the buffer immediately without waiting on a fence. If
* a valid file descriptor is returned (i.e. any value except -1) then the
* caller is responsible for closing the file descriptor.
*
* Returns 0 on success or -errno on error.
*/
int (*dequeueBuffer)(struct ANativeWindow *window,
struct ANativeWindowBuffer **buffer, int *fenceFd);
/*
* Hook called by EGL when modifications to the render buffer are done.
* This unlocks and post the buffer.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* The fenceFd argument specifies a libsync fence file descriptor for a
* fence that must signal before the buffer can be accessed. If the buffer
* can be accessed immediately then a value of -1 should be used. The
* caller must not use the file descriptor after it is passed to
* queueBuffer, and the ANativeWindow implementation is responsible for
* closing it.
*
* Returns 0 on success or -errno on error.
*/
int (*queueBuffer)(struct ANativeWindow *window,
struct ANativeWindowBuffer *buffer, int fenceFd);
/*
* Hook used to cancel a buffer that has been dequeued.
* No synchronization is performed between dequeue() and cancel(), so
* either external synchronization is needed, or these functions must be
* called from the same thread.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* The fenceFd argument specifies a libsync fence file decsriptor for a
* fence that must signal before the buffer can be accessed. If the buffer
* can be accessed immediately then a value of -1 should be used.
*
* Note that if the client has not waited on the fence that was returned
* from dequeueBuffer, that same fence should be passed to cancelBuffer to
* ensure that future uses of the buffer are preceded by a wait on that
* fence. The caller must not use the file descriptor after it is passed
* to cancelBuffer, and the ANativeWindow implementation is responsible for
* closing it.
*
* Returns 0 on success or -errno on error.
*/
int (*cancelBuffer)(struct ANativeWindow *window,
struct ANativeWindowBuffer *buffer, int fenceFd);
};

View File

@ -4,15 +4,21 @@
#include <android/native_window_jni.h> #include <android/native_window_jni.h>
#include <android/choreographer.h> #include <android/choreographer.h>
#include <common/settings.h> #include <common/settings.h>
#include <common/signal.h>
#include <jvm.h> #include <jvm.h>
#include <gpu.h> #include <gpu.h>
#include <loader/loader.h>
#include <kernel/types/KProcess.h>
#include "presentation_engine.h" #include "presentation_engine.h"
#include "native_window.h"
#include "texture/format.h" #include "texture/format.h"
extern skyline::i32 Fps; extern skyline::i32 Fps;
extern skyline::i32 FrameTime; extern skyline::i32 FrameTime;
namespace skyline::gpu { namespace skyline::gpu {
using namespace service::hosbinder;
PresentationEngine::PresentationEngine(const DeviceState &state, GPU &gpu) : state(state), gpu(gpu), acquireFence(gpu.vkDevice, vk::FenceCreateInfo{}), presentationTrack(static_cast<u64>(trace::TrackIds::Presentation), perfetto::ProcessTrack::Current()), choreographerThread(&PresentationEngine::ChoreographerThread, this), vsyncEvent(std::make_shared<kernel::type::KEvent>(state, true)) { PresentationEngine::PresentationEngine(const DeviceState &state, GPU &gpu) : state(state), gpu(gpu), acquireFence(gpu.vkDevice, vk::FenceCreateInfo{}), presentationTrack(static_cast<u64>(trace::TrackIds::Presentation), perfetto::ProcessTrack::Current()), choreographerThread(&PresentationEngine::ChoreographerThread, this), vsyncEvent(std::make_shared<kernel::type::KEvent>(state, true)) {
auto desc{presentationTrack.Serialize()}; auto desc{presentationTrack.Serialize()};
desc.set_name("Presentation"); desc.set_name("Presentation");
@ -31,22 +37,44 @@ namespace skyline::gpu {
} }
} }
/** void PresentationEngine::ChoreographerCallback(long frameTimeNanos, PresentationEngine *engine) {
* @url https://developer.android.com/ndk/reference/group/choreographer#achoreographer_framecallback u64 cycleLength{frameTimeNanos - engine->lastChoreographerTime};
*/ if (std::abs(static_cast<i64>(cycleLength - engine->refreshCycleDuration)) > (constant::NsInMillisecond / 2))
void ChoreographerCallback(long frameTimeNanos, kernel::type::KEvent *vsyncEvent) { if (engine->window)
vsyncEvent->Signal(); engine->window->perform(engine->window, NATIVE_WINDOW_GET_REFRESH_CYCLE_DURATION, &engine->refreshCycleDuration);
AChoreographer_postFrameCallback(AChoreographer_getInstance(), reinterpret_cast<AChoreographer_frameCallback>(&ChoreographerCallback), vsyncEvent); else
engine->refreshCycleDuration = cycleLength;
engine->lastChoreographerTime = frameTimeNanos;
engine->vsyncEvent->Signal();
AChoreographer_postFrameCallback(AChoreographer_getInstance(), reinterpret_cast<AChoreographer_frameCallback>(&ChoreographerCallback), engine);
} }
void PresentationEngine::ChoreographerThread() { void PresentationEngine::ChoreographerThread() {
pthread_setname_np(pthread_self(), "Skyline-Choreographer");
try {
signal::SetSignalHandler({SIGINT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV}, signal::ExceptionalSignalHandler);
choreographerLooper = ALooper_prepare(0); choreographerLooper = ALooper_prepare(0);
AChoreographer_postFrameCallback(AChoreographer_getInstance(), reinterpret_cast<AChoreographer_frameCallback>(&ChoreographerCallback), vsyncEvent.get()); AChoreographer_postFrameCallback(AChoreographer_getInstance(), reinterpret_cast<AChoreographer_frameCallback>(&ChoreographerCallback), this);
ALooper_pollAll(-1, nullptr, nullptr, nullptr); // Will block and process callbacks till ALooper_wake() is called ALooper_pollAll(-1, nullptr, nullptr, nullptr); // Will block and process callbacks till ALooper_wake() is called
} catch (const signal::SignalException &e) {
state.logger->Error("{}\nStack Trace:{}", e.what(), state.loader->GetStackTrace(e.frames));
if (state.process)
state.process->Kill(false);
else
std::rethrow_exception(std::current_exception());
} catch (const std::exception &e) {
state.logger->Error(e.what());
if (state.process)
state.process->Kill(false);
else
std::rethrow_exception(std::current_exception());
}
} }
service::hosbinder::NativeWindowTransform GetAndroidTransform(vk::SurfaceTransformFlagBitsKHR transform) { NativeWindowTransform GetAndroidTransform(vk::SurfaceTransformFlagBitsKHR transform) {
using NativeWindowTransform = service::hosbinder::NativeWindowTransform; using NativeWindowTransform = NativeWindowTransform;
switch (transform) { switch (transform) {
case vk::SurfaceTransformFlagBitsKHR::eIdentity: case vk::SurfaceTransformFlagBitsKHR::eIdentity:
case vk::SurfaceTransformFlagBitsKHR::eInherit: case vk::SurfaceTransformFlagBitsKHR::eInherit:
@ -106,7 +134,6 @@ namespace skyline::gpu {
auto vkImages{vkSwapchain->getImages()}; auto vkImages{vkSwapchain->getImages()};
if (vkImages.size() > MaxSwapchainImageCount) if (vkImages.size() > MaxSwapchainImageCount)
throw exception("Swapchain has higher image count ({}) than maximum slot count ({})", minImageCount, MaxSwapchainImageCount); throw exception("Swapchain has higher image count ({}) than maximum slot count ({})", minImageCount, MaxSwapchainImageCount);
state.logger->Error("Buffer Count: {}", vkImages.size());
for (size_t index{}; index < vkImages.size(); index++) { for (size_t index{}; index < vkImages.size(); index++) {
auto &slot{images[index]}; auto &slot{images[index]};
@ -135,8 +162,9 @@ namespace skyline::gpu {
vkSwapchain.reset(); vkSwapchain.reset();
if (jSurface) { if (jSurface) {
window = ANativeWindow_fromSurface(env, jSurface);
vkSurface.emplace(gpu.vkInstance, vk::AndroidSurfaceCreateInfoKHR{ vkSurface.emplace(gpu.vkInstance, vk::AndroidSurfaceCreateInfoKHR{
.window = ANativeWindow_fromSurface(env, jSurface), .window = window,
}); });
if (!gpu.vkPhysicalDevice.getSurfaceSupportKHR(gpu.vkQueueFamilyIndex, **vkSurface)) if (!gpu.vkPhysicalDevice.getSurfaceSupportKHR(gpu.vkQueueFamilyIndex, **vkSurface))
throw exception("Vulkan Queue doesn't support presentation with surface"); throw exception("Vulkan Queue doesn't support presentation with surface");
@ -145,19 +173,51 @@ namespace skyline::gpu {
if (swapchainExtent && swapchainFormat) if (swapchainExtent && swapchainFormat)
UpdateSwapchain(swapchainFormat, swapchainExtent); UpdateSwapchain(swapchainFormat, swapchainExtent);
if (window->common.magic != AndroidNativeWindowMagic)
throw exception("ANativeWindow* has unexpected magic: {} instead of {}", span(&window->common.magic, 1).as_string(true), span<const u8>(reinterpret_cast<const u8 *>(&AndroidNativeWindowMagic), sizeof(u32)).as_string(true));
if (window->common.version != sizeof(ANativeWindow))
throw exception("ANativeWindow* has unexpected version: {} instead of {}", window->common.version, sizeof(ANativeWindow));
if (windowCrop)
window->perform(window, NATIVE_WINDOW_SET_CROP, &windowCrop);
if (windowScalingMode != NativeWindowScalingMode::ScaleToWindow)
window->perform(window, NATIVE_WINDOW_SET_SCALING_MODE, static_cast<i32>(windowScalingMode));
window->perform(window, NATIVE_WINDOW_ENABLE_FRAME_TIMESTAMPS, true);
surfaceCondition.notify_all(); surfaceCondition.notify_all();
} else { } else {
vkSurface.reset(); vkSurface.reset();
window = nullptr;
} }
} }
void PresentationEngine::Present(const std::shared_ptr<Texture> &texture, u64 presentId) { void PresentationEngine::Present(const std::shared_ptr<Texture> &texture, u64 timestamp, u64 swapInterval, AndroidRect crop, NativeWindowScalingMode scalingMode, NativeWindowTransform transform, u64 &frameId) {
std::unique_lock lock(mutex); std::unique_lock lock(mutex);
surfaceCondition.wait(lock, [this]() { return vkSurface.has_value(); }); surfaceCondition.wait(lock, [this]() { return vkSurface.has_value(); });
if (texture->format != swapchainFormat || texture->dimensions != swapchainExtent) if (texture->format != swapchainFormat || texture->dimensions != swapchainExtent)
UpdateSwapchain(texture->format, texture->dimensions); UpdateSwapchain(texture->format, texture->dimensions);
int result;
if (crop && crop != windowCrop) {
if ((result = window->perform(window, NATIVE_WINDOW_SET_CROP, &crop)))
throw exception("Setting the layer crop to ({}-{})x({}-{}) failed with {}", crop.left, crop.right, crop.top, crop.bottom, result);
windowCrop = crop;
}
if (scalingMode != NativeWindowScalingMode::Freeze && windowScalingMode != scalingMode) {
if ((result = window->perform(window, NATIVE_WINDOW_SET_SCALING_MODE, static_cast<i32>(scalingMode))))
throw exception("Setting the layer scaling mode to '{}' failed with {}", ToString(scalingMode), result);
windowScalingMode = scalingMode;
}
if (transform != windowTransform) {
if ((result = window->perform(window, NATIVE_WINDOW_SET_BUFFERS_TRANSFORM, static_cast<i32>(transform))))
throw exception("Setting the buffer transform to '{}' failed with {}", ToString(transform), result);
windowTransform = transform;
}
std::pair<vk::Result, u32> nextImage; std::pair<vk::Result, u32> nextImage;
while (nextImage = vkSwapchain->acquireNextImage(std::numeric_limits<u64>::max(), {}, *acquireFence), nextImage.first != vk::Result::eSuccess) [[unlikely]] { while (nextImage = vkSwapchain->acquireNextImage(std::numeric_limits<u64>::max(), {}, *acquireFence), nextImage.first != vk::Result::eSuccess) [[unlikely]] {
if (nextImage.first == vk::Result::eSuboptimalKHR) if (nextImage.first == vk::Result::eSuboptimalKHR)
@ -166,16 +226,47 @@ namespace skyline::gpu {
throw exception("vkAcquireNextImageKHR returned an unhandled result '{}'", vk::to_string(nextImage.first)); throw exception("vkAcquireNextImageKHR returned an unhandled result '{}'", vk::to_string(nextImage.first));
} }
static_cast<void>(gpu.vkDevice.waitForFences(*acquireFence, true, std::numeric_limits<u64>::max())); std::ignore = gpu.vkDevice.waitForFences(*acquireFence, true, std::numeric_limits<u64>::max());
images.at(nextImage.second)->CopyFrom(texture); images.at(nextImage.second)->CopyFrom(texture);
if (timestamp) {
// If the timestamp is specified, we need to convert it from the util::GetTimeNs base to the CLOCK_MONOTONIC one
// We do so by getting an offset from the current time in nanoseconds and then adding it to the current time in CLOCK_MONOTONIC
// Note: It's important we do this right before present as going past the timestamp could lead to fewer Binder IPC calls
auto current{util::GetTimeNs()};
if (current < timestamp) {
timespec time;
if (clock_gettime(CLOCK_MONOTONIC, &time))
throw exception("Failed to clock_gettime with '{}'", strerror(errno));
timestamp = ((time.tv_sec * constant::NsInSecond) + time.tv_nsec) + (timestamp - current);
} else {
timestamp = 0;
}
}
if (swapInterval > 1)
// If we have a swap interval above 1 we have to adjust the timestamp to emulate the swap interval
timestamp = std::max(timestamp, lastChoreographerTime + (refreshCycleDuration * swapInterval * 2));
auto lastTimestamp{std::exchange(windowLastTimestamp, timestamp)};
if (!timestamp && lastTimestamp)
// We need to nullify the timestamp if it transitioned from being specified (non-zero) to unspecified (zero)
timestamp = NativeWindowTimestampAuto;
if (timestamp)
if (window->perform(window, NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP, timestamp))
throw exception("Setting the buffer timestamp to {} failed with {}", timestamp, result);
if ((result = window->perform(window, NATIVE_WINDOW_GET_NEXT_FRAME_ID, &frameId)))
throw exception("Retrieving the next frame's ID failed with {}", result);
{ {
std::lock_guard queueLock(gpu.queueMutex); std::lock_guard queueLock(gpu.queueMutex);
static_cast<void>(gpu.vkQueue.presentKHR(vk::PresentInfoKHR{ std::ignore = gpu.vkQueue.presentKHR(vk::PresentInfoKHR{
.swapchainCount = 1, .swapchainCount = 1,
.pSwapchains = &**vkSwapchain, .pSwapchains = &**vkSwapchain,
.pImageIndices = &nextImage.second, .pImageIndices = &nextImage.second,
})); // We explicitly discard the result here as suboptimal images are expected when the game doesn't respect the transform hint }); // We don't care about suboptimal images as they are caused by not respecting the transform hint, we handle transformations externally
} }
if (frameTimestamp) { if (frameTimestamp) {
@ -191,7 +282,7 @@ namespace skyline::gpu {
} }
} }
service::hosbinder::NativeWindowTransform PresentationEngine::GetTransformHint() { NativeWindowTransform PresentationEngine::GetTransformHint() {
std::unique_lock lock(mutex); std::unique_lock lock(mutex);
surfaceCondition.wait(lock, [this]() { return vkSurface.has_value(); }); surfaceCondition.wait(lock, [this]() { return vkSurface.has_value(); });
return GetAndroidTransform(vkSurfaceCapabilities.currentTransform); return GetAndroidTransform(vkSurfaceCapabilities.currentTransform);

View File

@ -23,6 +23,11 @@ namespace skyline::gpu {
std::mutex mutex; //!< Synchronizes access to the surface objects std::mutex mutex; //!< Synchronizes access to the surface objects
std::condition_variable surfaceCondition; //!< Allows us to efficiently wait for Vulkan surface to be initialized std::condition_variable surfaceCondition; //!< Allows us to efficiently wait for Vulkan surface to be initialized
jobject jSurface{}; //!< The Java Surface object backing the ANativeWindow jobject jSurface{}; //!< The Java Surface object backing the ANativeWindow
ANativeWindow* window{}; //!< A pointer to an Android Native Window which is the surface we draw to
service::hosbinder::AndroidRect windowCrop{}; //!< A rectangle with the bounds of the current crop performed on the image prior to presentation
service::hosbinder::NativeWindowScalingMode windowScalingMode{service::hosbinder::NativeWindowScalingMode::ScaleToWindow}; //!< The mode in which the cropped image is scaled up to the surface
service::hosbinder::NativeWindowTransform windowTransform{}; //!< The transformation performed on the image prior to presentation
u64 windowLastTimestamp{}; //!< The last timestamp submitted to the window, 0 or CLOCK_MONOTONIC value
std::optional<vk::raii::SurfaceKHR> vkSurface; //!< The Vulkan Surface object that is backed by ANativeWindow std::optional<vk::raii::SurfaceKHR> vkSurface; //!< The Vulkan Surface object that is backed by ANativeWindow
vk::SurfaceCapabilitiesKHR vkSurfaceCapabilities; //!< The capabilities of the current Vulkan Surface vk::SurfaceCapabilitiesKHR vkSurfaceCapabilities; //!< The capabilities of the current Vulkan Surface
@ -40,6 +45,13 @@ namespace skyline::gpu {
std::thread choreographerThread; //!< A thread for signalling the V-Sync event using AChoreographer std::thread choreographerThread; //!< A thread for signalling the V-Sync event using AChoreographer
ALooper *choreographerLooper{}; //!< The looper object associated with the Choreographer thread ALooper *choreographerLooper{}; //!< The looper object associated with the Choreographer thread
u64 lastChoreographerTime{}; //!< The timestamp of the last invocation of Choreographer::doFrame
u64 refreshCycleDuration{}; //!< The duration of a single refresh cycle for the display in nanoseconds
/**
* @url https://developer.android.com/ndk/reference/group/choreographer#achoreographer_framecallback
*/
static void ChoreographerCallback(long frameTimeNanos, PresentationEngine *engine);
/** /**
* @brief The entry point for the the Choreographer thread, the function runs ALooper on the thread * @brief The entry point for the the Choreographer thread, the function runs ALooper on the thread
@ -65,10 +77,15 @@ namespace skyline::gpu {
/** /**
* @brief Queue the supplied texture to be presented to the screen * @brief Queue the supplied texture to be presented to the screen
* @param presentId A UUID used to tag this frame for presentation timing readouts * @param timestamp The earliest timestamp (relative to skyline::util::GetTickNs) at which the frame must be presented, it should be 0 when it doesn't matter
* @param swapInterval The amount of display refreshes that must take place prior to presenting this image
* @param crop A rectangle with bounds that the image will be cropped to
* @param scalingMode The mode by which the image must be scaled up to the surface
* @param transform A transformation that should be performed on the image
* @param frameId The ID of this frame for correlating it with presentation timing readouts
* @note The texture **must** be locked prior to calling this * @note The texture **must** be locked prior to calling this
*/ */
void Present(const std::shared_ptr<Texture> &texture, u64 presentId); void Present(const std::shared_ptr<Texture> &texture, u64 timestamp, u64 swapInterval, service::hosbinder::AndroidRect crop, service::hosbinder::NativeWindowScalingMode scalingMode, service::hosbinder::NativeWindowTransform transform, u64& frameId);
/** /**
* @return A transform that the application should render with to elide costly transforms later * @return A transform that the application should render with to elide costly transforms later

View File

@ -718,7 +718,6 @@ namespace skyline::kernel::svc {
} }
state.logger->Debug("Locking 0x{:X}", mutex); state.logger->Debug("Locking 0x{:X}", mutex);
TRACE_EVENT_FMT("kernel", "MutexLock 0x{:X}", mutex);
KHandle ownerHandle{state.ctx->gpr.w0}; KHandle ownerHandle{state.ctx->gpr.w0};
KHandle requesterHandle{state.ctx->gpr.w2}; KHandle requesterHandle{state.ctx->gpr.w2};
@ -741,8 +740,6 @@ namespace skyline::kernel::svc {
return; return;
} }
TRACE_EVENT_FMT("kernel", "MutexUnlock 0x{:X}", mutex);
state.logger->Debug("Unlocking 0x{:X}", mutex); state.logger->Debug("Unlocking 0x{:X}", mutex);
state.process->MutexUnlock(mutex); state.process->MutexUnlock(mutex);
state.logger->Debug("Unlocked 0x{:X}", mutex); state.logger->Debug("Unlocked 0x{:X}", mutex);

View File

@ -3,11 +3,9 @@
// Copyright © 2005 The Android Open Source Project // Copyright © 2005 The Android Open Source Project
// Copyright © 2019-2020 Ryujinx Team and Contributors // Copyright © 2019-2020 Ryujinx Team and Contributors
#include <android/hardware_buffer.h>
#include <gpu.h> #include <gpu.h>
#include <gpu/texture/format.h> #include <gpu/texture/format.h>
#include <soc.h> #include <soc.h>
#include <common/settings.h>
#include <services/nvdrv/driver.h> #include <services/nvdrv/driver.h>
#include <services/nvdrv/devices/nvmap.h> #include <services/nvdrv/devices/nvmap.h>
#include <services/common/fence.h> #include <services/common/fence.h>
@ -163,10 +161,11 @@ namespace skyline::service::hosbinder {
auto &texture{buffer.texture}; auto &texture{buffer.texture};
std::scoped_lock textureLock(*texture); std::scoped_lock textureLock(*texture);
texture->SynchronizeHost(); texture->SynchronizeHost();
state.gpu->presentation.Present(texture, ++frameNumber); u64 frameId;
state.gpu->presentation.Present(texture, isAutoTimestamp ? 0 : timestamp, swapInterval, crop, scalingMode, transform, frameId);
} }
buffer.frameNumber = frameNumber; buffer.frameNumber = ++frameNumber;
buffer.state = BufferState::Free; buffer.state = BufferState::Free;
bufferEvent->Signal(); bufferEvent->Signal();

View File

@ -81,6 +81,15 @@ namespace skyline::service::hosbinder {
u32 top; u32 top;
u32 right; u32 right;
u32 bottom; u32 bottom;
/**
* @return If the rectangle had any defined bounds
*/
constexpr operator bool() {
return left || top || right || bottom;
}
auto operator<=>(const AndroidRect &) const = default;
}; };
/** /**