diff --git a/app/src/main/cpp/skyline/common.h b/app/src/main/cpp/skyline/common.h index 77becb73..5e991af7 100644 --- a/app/src/main/cpp/skyline/common.h +++ b/app/src/main/cpp/skyline/common.h @@ -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 */ constexpr std::string_view as_string(bool nullTerminated = false) { - return std::string_view(reinterpret_cast(span::data()), nullTerminated ? (std::find(span::begin(), span::end(), 0) - span::begin()) : span::size_bytes()); + return std::string_view(reinterpret_cast(span::data()), nullTerminated ? (std::find(span::begin(), span::end(), 0) - span::begin()) : span::size_bytes()); } template diff --git a/app/src/main/cpp/skyline/gpu/native_window.h b/app/src/main/cpp/skyline/gpu/native_window.h new file mode 100644 index 00000000..695b0272 --- /dev/null +++ b/app/src/main/cpp/skyline/gpu/native_window.h @@ -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(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); +}; diff --git a/app/src/main/cpp/skyline/gpu/presentation_engine.cpp b/app/src/main/cpp/skyline/gpu/presentation_engine.cpp index 6a9e1543..ef404071 100644 --- a/app/src/main/cpp/skyline/gpu/presentation_engine.cpp +++ b/app/src/main/cpp/skyline/gpu/presentation_engine.cpp @@ -4,15 +4,21 @@ #include #include #include +#include #include #include +#include +#include #include "presentation_engine.h" +#include "native_window.h" #include "texture/format.h" extern skyline::i32 Fps; extern skyline::i32 FrameTime; 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(trace::TrackIds::Presentation), perfetto::ProcessTrack::Current()), choreographerThread(&PresentationEngine::ChoreographerThread, this), vsyncEvent(std::make_shared(state, true)) { auto desc{presentationTrack.Serialize()}; desc.set_name("Presentation"); @@ -31,22 +37,44 @@ namespace skyline::gpu { } } - /** - * @url https://developer.android.com/ndk/reference/group/choreographer#achoreographer_framecallback - */ - void ChoreographerCallback(long frameTimeNanos, kernel::type::KEvent *vsyncEvent) { - vsyncEvent->Signal(); - AChoreographer_postFrameCallback(AChoreographer_getInstance(), reinterpret_cast(&ChoreographerCallback), vsyncEvent); + void PresentationEngine::ChoreographerCallback(long frameTimeNanos, PresentationEngine *engine) { + u64 cycleLength{frameTimeNanos - engine->lastChoreographerTime}; + if (std::abs(static_cast(cycleLength - engine->refreshCycleDuration)) > (constant::NsInMillisecond / 2)) + if (engine->window) + engine->window->perform(engine->window, NATIVE_WINDOW_GET_REFRESH_CYCLE_DURATION, &engine->refreshCycleDuration); + else + engine->refreshCycleDuration = cycleLength; + + engine->lastChoreographerTime = frameTimeNanos; + engine->vsyncEvent->Signal(); + + AChoreographer_postFrameCallback(AChoreographer_getInstance(), reinterpret_cast(&ChoreographerCallback), engine); } void PresentationEngine::ChoreographerThread() { - choreographerLooper = ALooper_prepare(0); - AChoreographer_postFrameCallback(AChoreographer_getInstance(), reinterpret_cast(&ChoreographerCallback), vsyncEvent.get()); - ALooper_pollAll(-1, nullptr, nullptr, nullptr); // Will block and process callbacks till ALooper_wake() is called + pthread_setname_np(pthread_self(), "Skyline-Choreographer"); + try { + signal::SetSignalHandler({SIGINT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV}, signal::ExceptionalSignalHandler); + choreographerLooper = ALooper_prepare(0); + AChoreographer_postFrameCallback(AChoreographer_getInstance(), reinterpret_cast(&ChoreographerCallback), this); + 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) { - using NativeWindowTransform = service::hosbinder::NativeWindowTransform; + NativeWindowTransform GetAndroidTransform(vk::SurfaceTransformFlagBitsKHR transform) { + using NativeWindowTransform = NativeWindowTransform; switch (transform) { case vk::SurfaceTransformFlagBitsKHR::eIdentity: case vk::SurfaceTransformFlagBitsKHR::eInherit: @@ -106,7 +134,6 @@ namespace skyline::gpu { auto vkImages{vkSwapchain->getImages()}; if (vkImages.size() > 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++) { auto &slot{images[index]}; @@ -135,8 +162,9 @@ namespace skyline::gpu { vkSwapchain.reset(); if (jSurface) { + window = ANativeWindow_fromSurface(env, jSurface); vkSurface.emplace(gpu.vkInstance, vk::AndroidSurfaceCreateInfoKHR{ - .window = ANativeWindow_fromSurface(env, jSurface), + .window = window, }); if (!gpu.vkPhysicalDevice.getSurfaceSupportKHR(gpu.vkQueueFamilyIndex, **vkSurface)) throw exception("Vulkan Queue doesn't support presentation with surface"); @@ -145,19 +173,51 @@ namespace skyline::gpu { if (swapchainExtent && swapchainFormat) 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(reinterpret_cast(&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(windowScalingMode)); + + window->perform(window, NATIVE_WINDOW_ENABLE_FRAME_TIMESTAMPS, true); + surfaceCondition.notify_all(); } else { vkSurface.reset(); + window = nullptr; } } - void PresentationEngine::Present(const std::shared_ptr &texture, u64 presentId) { + void PresentationEngine::Present(const std::shared_ptr &texture, u64 timestamp, u64 swapInterval, AndroidRect crop, NativeWindowScalingMode scalingMode, NativeWindowTransform transform, u64 &frameId) { std::unique_lock lock(mutex); surfaceCondition.wait(lock, [this]() { return vkSurface.has_value(); }); if (texture->format != swapchainFormat || texture->dimensions != swapchainExtent) 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(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(transform)))) + throw exception("Setting the buffer transform to '{}' failed with {}", ToString(transform), result); + windowTransform = transform; + } + std::pair nextImage; while (nextImage = vkSwapchain->acquireNextImage(std::numeric_limits::max(), {}, *acquireFence), nextImage.first != vk::Result::eSuccess) [[unlikely]] { 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)); } - static_cast(gpu.vkDevice.waitForFences(*acquireFence, true, std::numeric_limits::max())); + std::ignore = gpu.vkDevice.waitForFences(*acquireFence, true, std::numeric_limits::max()); 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); - static_cast(gpu.vkQueue.presentKHR(vk::PresentInfoKHR{ + std::ignore = gpu.vkQueue.presentKHR(vk::PresentInfoKHR{ .swapchainCount = 1, .pSwapchains = &**vkSwapchain, .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) { @@ -191,7 +282,7 @@ namespace skyline::gpu { } } - service::hosbinder::NativeWindowTransform PresentationEngine::GetTransformHint() { + NativeWindowTransform PresentationEngine::GetTransformHint() { std::unique_lock lock(mutex); surfaceCondition.wait(lock, [this]() { return vkSurface.has_value(); }); return GetAndroidTransform(vkSurfaceCapabilities.currentTransform); diff --git a/app/src/main/cpp/skyline/gpu/presentation_engine.h b/app/src/main/cpp/skyline/gpu/presentation_engine.h index 95b9e95b..afc6df3d 100644 --- a/app/src/main/cpp/skyline/gpu/presentation_engine.h +++ b/app/src/main/cpp/skyline/gpu/presentation_engine.h @@ -23,6 +23,11 @@ namespace skyline::gpu { std::mutex mutex; //!< Synchronizes access to the surface objects std::condition_variable surfaceCondition; //!< Allows us to efficiently wait for Vulkan surface to be initialized 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 vkSurface; //!< The Vulkan Surface object that is backed by ANativeWindow 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 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 @@ -65,10 +77,15 @@ namespace skyline::gpu { /** * @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 */ - void Present(const std::shared_ptr &texture, u64 presentId); + void Present(const std::shared_ptr &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 diff --git a/app/src/main/cpp/skyline/kernel/svc.cpp b/app/src/main/cpp/skyline/kernel/svc.cpp index c24ad589..f74acda8 100644 --- a/app/src/main/cpp/skyline/kernel/svc.cpp +++ b/app/src/main/cpp/skyline/kernel/svc.cpp @@ -718,7 +718,6 @@ namespace skyline::kernel::svc { } state.logger->Debug("Locking 0x{:X}", mutex); - TRACE_EVENT_FMT("kernel", "MutexLock 0x{:X}", mutex); KHandle ownerHandle{state.ctx->gpr.w0}; KHandle requesterHandle{state.ctx->gpr.w2}; @@ -741,8 +740,6 @@ namespace skyline::kernel::svc { return; } - TRACE_EVENT_FMT("kernel", "MutexUnlock 0x{:X}", mutex); - state.logger->Debug("Unlocking 0x{:X}", mutex); state.process->MutexUnlock(mutex); state.logger->Debug("Unlocked 0x{:X}", mutex); diff --git a/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.cpp b/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.cpp index d8ffd5a0..d581a0c7 100644 --- a/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.cpp +++ b/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.cpp @@ -3,11 +3,9 @@ // Copyright © 2005 The Android Open Source Project // Copyright © 2019-2020 Ryujinx Team and Contributors -#include #include #include #include -#include #include #include #include @@ -163,10 +161,11 @@ namespace skyline::service::hosbinder { auto &texture{buffer.texture}; std::scoped_lock textureLock(*texture); 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; bufferEvent->Signal(); diff --git a/app/src/main/cpp/skyline/services/hosbinder/android_types.h b/app/src/main/cpp/skyline/services/hosbinder/android_types.h index 200758d4..c37855bc 100644 --- a/app/src/main/cpp/skyline/services/hosbinder/android_types.h +++ b/app/src/main/cpp/skyline/services/hosbinder/android_types.h @@ -81,6 +81,15 @@ namespace skyline::service::hosbinder { u32 top; u32 right; u32 bottom; + + /** + * @return If the rectangle had any defined bounds + */ + constexpr operator bool() { + return left || top || right || bottom; + } + + auto operator<=>(const AndroidRect &) const = default; }; /**