mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-17 20:00:46 +02:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5716ab09f | ||
|
|
854969457f |
No files matched your search
@@ -57,7 +57,7 @@ jobs:
|
||||
cmake git ninja pkgconf lld sdl3 sdl3_ttf curl openssl \
|
||||
nlohmann-json libyaml libelf minizip minizip-ng capstone gtk4 \
|
||||
libadwaita webkitgtk-6.0 libutf8proc fontconfig libglvnd \
|
||||
vulkan-headers zlib libplacebo bubblewrap xdg-dbus-proxy \
|
||||
vulkan-headers zlib libplacebo libpng bubblewrap xdg-dbus-proxy \
|
||||
patchelf jq unzip file ca-certificates dbus zsync
|
||||
|
||||
# Reduced ABI-compatible packages belong only in this builder.
|
||||
@@ -172,7 +172,7 @@ jobs:
|
||||
libyaml-dev libelf-dev libminizip-dev libcapstone-dev \
|
||||
libgtk-4-dev libadwaita-1-dev libwebkitgtk-6.0-dev \
|
||||
libutf8proc-dev libfontconfig1-dev libegl-dev \
|
||||
libvulkan-dev libplacebo-dev zlib1g-dev
|
||||
libvulkan-dev libplacebo-dev libpng-dev zlib1g-dev
|
||||
cmake -S . -B build-deb -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
@@ -220,7 +220,7 @@ jobs:
|
||||
minizip-ng-compat-devel capstone-devel gtk4-devel libadwaita-devel \
|
||||
webkitgtk6.0-devel utf8proc-devel fontconfig-devel \
|
||||
libglvnd-devel vulkan-headers vulkan-loader-devel \
|
||||
libplacebo-devel zlib-ng-compat-devel
|
||||
libplacebo-devel libpng-devel zlib-ng-compat-devel
|
||||
cmake -S . -B build-rpm -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
@@ -264,7 +264,7 @@ jobs:
|
||||
cmake git ninja pkgconf lld sdl3 sdl3_ttf curl openssl \
|
||||
nlohmann-json libyaml libelf minizip capstone gtk4 \
|
||||
libadwaita webkitgtk-6.0 libutf8proc fontconfig \
|
||||
libplacebo vulkan-headers vulkan-icd-loader libglvnd zlib
|
||||
libplacebo libpng vulkan-headers vulkan-icd-loader libglvnd zlib
|
||||
useradd --create-home builder
|
||||
chown -R builder:builder /workspace
|
||||
runuser -u builder -- env MOCKTAIL_SOURCE_DIR=/workspace \
|
||||
|
||||
@@ -264,6 +264,7 @@ add_library(mocktail_runtime STATIC
|
||||
src/runtime/command_line.cc
|
||||
src/runtime/crash_report_policy.cc
|
||||
src/runtime/discord_rpc.cc
|
||||
src/runtime/device_memory_profile.cc
|
||||
src/runtime/device_profile.cc
|
||||
src/runtime/environment.cc
|
||||
src/runtime/external_launch_broker.cc
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#ifndef MOCKTAIL_GRAPHICS_ETC2_DECODER_H_
|
||||
#define MOCKTAIL_GRAPHICS_ETC2_DECODER_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace mocktail::graphics {
|
||||
|
||||
enum class EtcFormat {
|
||||
kEtc2Rgb8,
|
||||
kEtc2Rgb8A1,
|
||||
kEtc2Rgba8,
|
||||
kEacR11,
|
||||
kEacR11Signed,
|
||||
kEacRg11,
|
||||
kEacRg11Signed,
|
||||
};
|
||||
|
||||
std::size_t EtcBlockBytes(EtcFormat format);
|
||||
|
||||
std::size_t EtcDecodedTexelBytes(EtcFormat format);
|
||||
|
||||
bool DecodeEtcImage(EtcFormat format, const std::uint8_t* source,
|
||||
std::size_t source_bytes, std::uint32_t width,
|
||||
std::uint32_t height, std::uint8_t* destination,
|
||||
std::size_t destination_bytes);
|
||||
|
||||
bool DecodeEtcImageBlockRows(EtcFormat format, const std::uint8_t* source,
|
||||
std::size_t source_bytes, std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
std::uint32_t first_block_row,
|
||||
std::uint32_t block_row_count,
|
||||
std::uint8_t* destination,
|
||||
std::size_t destination_bytes);
|
||||
|
||||
struct EtcDecodeJob {
|
||||
EtcFormat format = EtcFormat::kEtc2Rgb8;
|
||||
const std::uint8_t* source = nullptr;
|
||||
std::size_t source_bytes = 0;
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
std::uint8_t* destination = nullptr;
|
||||
std::size_t destination_bytes = 0;
|
||||
bool ok = false;
|
||||
};
|
||||
|
||||
// Synchronous; job destination buffers must not overlap.
|
||||
void DecodeEtcJobs(EtcDecodeJob* jobs, std::size_t count,
|
||||
unsigned worker_count);
|
||||
|
||||
} // namespace mocktail::graphics
|
||||
|
||||
#endif // MOCKTAIL_GRAPHICS_ETC2_DECODER_H_
|
||||
@@ -0,0 +1,55 @@
|
||||
#ifndef MOCKTAIL_GRAPHICS_TEXTURE_OVERRIDE_H_
|
||||
#define MOCKTAIL_GRAPHICS_TEXTURE_OVERRIDE_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace mocktail::graphics {
|
||||
|
||||
struct RgbaImage {
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
std::vector<std::uint8_t> pixels;
|
||||
};
|
||||
|
||||
std::uint64_t HashBytes(const std::uint8_t* data, std::size_t size);
|
||||
std::string HashName(std::uint64_t hash);
|
||||
|
||||
bool ReadPngRgba(const std::string& path, RgbaImage* image);
|
||||
bool WritePngRgba(const std::string& path, const RgbaImage& image);
|
||||
|
||||
void ResampleRgba(const RgbaImage& source, std::uint32_t width,
|
||||
std::uint32_t height, std::uint8_t* destination);
|
||||
void ResampleRgba(const std::uint8_t* source, std::uint32_t source_width,
|
||||
std::uint32_t source_height, std::uint32_t width,
|
||||
std::uint32_t height, std::uint8_t* destination);
|
||||
|
||||
class TextureOverrides {
|
||||
public:
|
||||
TextureOverrides(std::string override_dir, std::string dump_dir);
|
||||
static TextureOverrides FromEnvironment();
|
||||
|
||||
bool enabled() const { return !override_dir_.empty() || !dump_dir_.empty(); }
|
||||
bool dumping() const { return !dump_dir_.empty(); }
|
||||
|
||||
std::shared_ptr<const RgbaImage> Lookup(std::uint64_t hash);
|
||||
void Dump(std::uint64_t hash, std::uint32_t width, std::uint32_t height,
|
||||
const std::uint8_t* rgba);
|
||||
|
||||
private:
|
||||
std::string override_dir_;
|
||||
std::string dump_dir_;
|
||||
std::mutex mutex_;
|
||||
std::unordered_map<std::uint64_t, std::shared_ptr<const RgbaImage>> cache_;
|
||||
std::unordered_set<std::uint64_t> dumped_;
|
||||
};
|
||||
|
||||
} // namespace mocktail::graphics
|
||||
|
||||
#endif // MOCKTAIL_GRAPHICS_TEXTURE_OVERRIDE_H_
|
||||
@@ -0,0 +1,102 @@
|
||||
#ifndef MOCKTAIL_GRAPHICS_VULKAN_ETC2_EMULATION_H_
|
||||
#define MOCKTAIL_GRAPHICS_VULKAN_ETC2_EMULATION_H_
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "mocktail/graphics/etc2_decoder.h"
|
||||
|
||||
namespace mocktail::graphics {
|
||||
|
||||
struct Etc2EmulatedFormat {
|
||||
VkFormat host_format = VK_FORMAT_UNDEFINED;
|
||||
EtcFormat etc_format = EtcFormat::kEtc2Rgb8;
|
||||
};
|
||||
|
||||
bool LookupEmulatedEtc2Format(VkFormat format, Etc2EmulatedFormat* out);
|
||||
|
||||
VkFormatFeatureFlags EmulatedEtc2FormatFeatures(
|
||||
VkFormatFeatureFlags host_features);
|
||||
|
||||
bool Etc2UploadByteCounts(EtcFormat format, const VkExtent3D& extent,
|
||||
std::uint32_t layer_count, VkDeviceSize* compressed,
|
||||
VkDeviceSize* decoded);
|
||||
|
||||
bool Etc2SupportAdvertised(const char* value);
|
||||
bool Etc2SupportAdvertised();
|
||||
|
||||
inline constexpr std::uint32_t kSmallTextureMaxExtent = 64;
|
||||
inline constexpr std::uint32_t kDefaultSmallTextureUpscale = 4;
|
||||
std::uint32_t SmallTextureUpscale(const char* value);
|
||||
|
||||
class VulkanEtc2Emulation final {
|
||||
public:
|
||||
VulkanEtc2Emulation();
|
||||
~VulkanEtc2Emulation();
|
||||
VulkanEtc2Emulation(const VulkanEtc2Emulation&) = delete;
|
||||
VulkanEtc2Emulation& operator=(const VulkanEtc2Emulation&) = delete;
|
||||
|
||||
bool PhysicalDeviceNeedsEmulation(VkPhysicalDevice physical_device,
|
||||
PFN_vkGetPhysicalDeviceFeatures host);
|
||||
|
||||
void RegisterDevice(VkDevice device, VkPhysicalDevice physical_device,
|
||||
bool emulated,
|
||||
const VkPhysicalDeviceMemoryProperties& memory,
|
||||
PFN_vkGetDeviceProcAddr get_device_proc_addr);
|
||||
void DestroyDevice(VkDevice device);
|
||||
|
||||
VkResult CreateImage(VkDevice device, const VkImageCreateInfo* create_info,
|
||||
const VkAllocationCallbacks* allocator, VkImage* image);
|
||||
void DestroyImage(VkDevice device, VkImage image,
|
||||
const VkAllocationCallbacks* allocator);
|
||||
VkResult CreateImageView(VkDevice device,
|
||||
const VkImageViewCreateInfo* create_info,
|
||||
const VkAllocationCallbacks* allocator,
|
||||
VkImageView* view);
|
||||
VkResult BindBufferMemory(VkDevice device, VkBuffer buffer,
|
||||
VkDeviceMemory memory, VkDeviceSize offset);
|
||||
VkResult BindBufferMemory2(VkDevice device, std::uint32_t count,
|
||||
const VkBindBufferMemoryInfo* infos);
|
||||
VkResult MapMemory(VkDevice device, VkDeviceMemory memory,
|
||||
VkDeviceSize offset, VkDeviceSize size,
|
||||
VkMemoryMapFlags flags, void** data);
|
||||
VkResult MapMemory2(VkDevice device, const VkMemoryMapInfo* info,
|
||||
void** data);
|
||||
void UnmapMemory(VkDevice device, VkDeviceMemory memory);
|
||||
VkResult UnmapMemory2(VkDevice device, const VkMemoryUnmapInfo* info);
|
||||
void FreeMemory(VkDevice device, VkDeviceMemory memory,
|
||||
const VkAllocationCallbacks* allocator);
|
||||
void DestroyBuffer(VkDevice device, VkBuffer buffer,
|
||||
const VkAllocationCallbacks* allocator);
|
||||
void CmdCopyBufferToImage(VkDevice device, VkCommandBuffer command_buffer,
|
||||
VkBuffer source, VkImage destination,
|
||||
VkImageLayout layout, std::uint32_t region_count,
|
||||
const VkBufferImageCopy* regions);
|
||||
void CmdCopyBufferToImage2(VkDevice device, VkCommandBuffer command_buffer,
|
||||
const VkCopyBufferToImageInfo2* info);
|
||||
void CmdCopyImage(VkDevice device, VkCommandBuffer command_buffer,
|
||||
VkImage source, VkImageLayout source_layout,
|
||||
VkImage destination, VkImageLayout destination_layout,
|
||||
std::uint32_t region_count, const VkImageCopy* regions);
|
||||
void CmdCopyImage2(VkDevice device, VkCommandBuffer command_buffer,
|
||||
const VkCopyImageInfo2* info);
|
||||
void CmdExecuteCommands(VkDevice device, VkCommandBuffer command_buffer,
|
||||
std::uint32_t count,
|
||||
const VkCommandBuffer* secondaries);
|
||||
|
||||
// Resubmission must reread the source bytes.
|
||||
void PrepareSubmit(const VkCommandBuffer* command_buffers,
|
||||
std::uint32_t count);
|
||||
// Call only after the command buffer is no longer pending.
|
||||
void ReleaseCommandBuffer(VkCommandBuffer command_buffer);
|
||||
|
||||
struct State;
|
||||
|
||||
private:
|
||||
State* state_;
|
||||
};
|
||||
|
||||
} // namespace mocktail::graphics
|
||||
|
||||
#endif // MOCKTAIL_GRAPHICS_VULKAN_ETC2_EMULATION_H_
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef MOCKTAIL_RUNTIME_DEVICE_MEMORY_PROFILE_H_
|
||||
#define MOCKTAIL_RUNTIME_DEVICE_MEMORY_PROFILE_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace mocktail {
|
||||
namespace runtime {
|
||||
|
||||
struct DeviceMemoryProfile {
|
||||
std::int32_t total_memory_mb = 0;
|
||||
std::int32_t memory_class_mb = 0;
|
||||
std::int32_t large_memory_class_mb = 0;
|
||||
std::int64_t low_memory_killer_background_threshold = 0;
|
||||
std::int64_t low_memory_killer_foreground_threshold = 0;
|
||||
bool low_ram_device = false;
|
||||
};
|
||||
|
||||
DeviceMemoryProfile BuildDeviceMemoryProfile(std::uint64_t host_memory_bytes);
|
||||
|
||||
} // namespace runtime
|
||||
} // namespace mocktail
|
||||
|
||||
#endif // MOCKTAIL_RUNTIME_DEVICE_MEMORY_PROFILE_H_
|
||||
@@ -0,0 +1,66 @@
|
||||
#ifndef MOCKTAIL_RUNTIME_DISPLAY_SIZE_H_
|
||||
#define MOCKTAIL_RUNTIME_DISPLAY_SIZE_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace mocktail {
|
||||
namespace runtime {
|
||||
|
||||
struct DisplaySize {
|
||||
std::int32_t width = 1920;
|
||||
std::int32_t height = 1080;
|
||||
};
|
||||
|
||||
// Physical pixels; window size is captured at startup.
|
||||
inline constexpr const char* kDisplaySizeEnvironment =
|
||||
"MOCKTAIL_DISPLAY_SIZE_INTERNAL";
|
||||
inline constexpr const char* kWindowSizeEnvironment =
|
||||
"MOCKTAIL_WINDOW_SIZE_INTERNAL";
|
||||
|
||||
inline DisplaySize ParseDisplaySize(const char* value) {
|
||||
const DisplaySize fallback;
|
||||
if (value == nullptr) {
|
||||
return fallback;
|
||||
}
|
||||
std::int64_t parts[2] = {0, 0};
|
||||
const char* cursor = value;
|
||||
for (int index = 0; index < 2; ++index) {
|
||||
if (*cursor < '0' || *cursor > '9') {
|
||||
return fallback;
|
||||
}
|
||||
std::int64_t parsed = 0;
|
||||
while (*cursor >= '0' && *cursor <= '9') {
|
||||
parsed = parsed * 10 + (*cursor - '0');
|
||||
if (parsed > std::numeric_limits<std::int32_t>::max()) {
|
||||
return fallback;
|
||||
}
|
||||
++cursor;
|
||||
}
|
||||
if (parsed == 0) {
|
||||
return fallback;
|
||||
}
|
||||
parts[index] = parsed;
|
||||
if (index == 0) {
|
||||
if (*cursor != 'x') {
|
||||
return fallback;
|
||||
}
|
||||
++cursor;
|
||||
}
|
||||
}
|
||||
if (*cursor != '\0') {
|
||||
return fallback;
|
||||
}
|
||||
return {static_cast<std::int32_t>(parts[0]),
|
||||
static_cast<std::int32_t>(parts[1])};
|
||||
}
|
||||
|
||||
inline std::int32_t PixelsToMillimetersAt160Dpi(std::int32_t pixels) {
|
||||
return static_cast<std::int32_t>(static_cast<std::int64_t>(pixels) * 254 /
|
||||
1600);
|
||||
}
|
||||
|
||||
} // namespace runtime
|
||||
} // namespace mocktail
|
||||
|
||||
#endif // MOCKTAIL_RUNTIME_DISPLAY_SIZE_H_
|
||||
@@ -16,6 +16,7 @@ depends=(
|
||||
'libelf'
|
||||
'libglvnd'
|
||||
'libplacebo'
|
||||
'libpng'
|
||||
'libutf8proc'
|
||||
'libyaml'
|
||||
'minizip'
|
||||
|
||||
@@ -17,6 +17,7 @@ pkgbase = mocktail-bin
|
||||
depends = libelf
|
||||
depends = libglvnd
|
||||
depends = libplacebo
|
||||
depends = libpng
|
||||
depends = libsoup3
|
||||
depends = libutf8proc
|
||||
depends = libyaml
|
||||
|
||||
@@ -20,6 +20,7 @@ depends=(
|
||||
'libelf'
|
||||
'libglvnd'
|
||||
'libplacebo'
|
||||
'libpng'
|
||||
'libsoup3'
|
||||
'libutf8proc'
|
||||
'libyaml'
|
||||
|
||||
@@ -24,6 +24,7 @@ pkgbase = mocktail-git
|
||||
depends = libelf
|
||||
depends = libglvnd
|
||||
depends = libplacebo
|
||||
depends = libpng
|
||||
depends = libsoup3
|
||||
depends = libutf8proc
|
||||
depends = libyaml
|
||||
|
||||
@@ -20,6 +20,7 @@ depends=(
|
||||
'libelf'
|
||||
'libglvnd'
|
||||
'libplacebo'
|
||||
'libpng'
|
||||
'libsoup3'
|
||||
'libutf8proc'
|
||||
'libyaml'
|
||||
|
||||
@@ -24,6 +24,7 @@ pkgbase = mocktail
|
||||
depends = libelf
|
||||
depends = libglvnd
|
||||
depends = libplacebo
|
||||
depends = libpng
|
||||
depends = libsoup3
|
||||
depends = libutf8proc
|
||||
depends = libyaml
|
||||
|
||||
@@ -20,6 +20,7 @@ depends=(
|
||||
'libelf'
|
||||
'libglvnd'
|
||||
'libplacebo'
|
||||
'libpng'
|
||||
'libsoup3'
|
||||
'libutf8proc'
|
||||
'libyaml'
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
elfutils,
|
||||
libglvnd,
|
||||
libplacebo,
|
||||
libpng,
|
||||
libsoup_3,
|
||||
libutf8proc,
|
||||
libyaml,
|
||||
@@ -92,6 +93,7 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
elfutils
|
||||
libglvnd
|
||||
libplacebo
|
||||
libpng
|
||||
libsoup_3
|
||||
libutf8proc
|
||||
libyaml
|
||||
|
||||
@@ -119,6 +119,7 @@ libyaml
|
||||
fontconfig
|
||||
minizip
|
||||
libplacebo
|
||||
libpng
|
||||
util-linux'
|
||||
INSTALL_ARGS='-S --needed --noconfirm'
|
||||
;;
|
||||
@@ -144,6 +145,7 @@ libyaml
|
||||
fontconfig
|
||||
minizip
|
||||
libplacebo
|
||||
libpng
|
||||
util-linux'
|
||||
if HostUsesMusl; then
|
||||
PACKAGES="$PACKAGES gcompat"
|
||||
@@ -172,6 +174,7 @@ libyaml
|
||||
fontconfig
|
||||
minizip
|
||||
libplacebo
|
||||
libpng
|
||||
coreutils
|
||||
findutils
|
||||
util-linux'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Includes changes by vii from CoderDayton/nightcap.
|
||||
// Android Vulkan loader ABI -> host Vulkan loader + SDL3 WSI.
|
||||
|
||||
#include <dlfcn.h>
|
||||
@@ -17,6 +18,7 @@
|
||||
|
||||
#include "mocktail/graphics/android_vulkan_wsi_adapter.h"
|
||||
#include "mocktail/graphics/present_mode_policy.h"
|
||||
#include "mocktail/graphics/vulkan_etc2_emulation.h"
|
||||
#include "mocktail/graphics/vulkan_text_overlay_compositor.h"
|
||||
#include "mocktail/platform/platform_runtime.h"
|
||||
|
||||
@@ -188,6 +190,7 @@ struct AdapterState {
|
||||
VkInstance latest_instance = VK_NULL_HANDLE;
|
||||
size_t active_instance_count = 0;
|
||||
mocktail::graphics::VulkanTextOverlayCompositor text_overlay;
|
||||
mocktail::graphics::VulkanEtc2Emulation etc2;
|
||||
std::atomic<NotePresentFn> note_present{nullptr};
|
||||
std::atomic<NoteHostPresentBeginFn> note_host_present_begin{nullptr};
|
||||
std::atomic<NoteHostPresentEndFn> note_host_present_end{nullptr};
|
||||
@@ -727,12 +730,90 @@ PFN_vkVoidFunction AdapterProc(const char* name) {
|
||||
MOCKTAIL_VK_PROC(vkQueueWaitIdle)
|
||||
MOCKTAIL_VK_PROC(vkDeviceWaitIdle)
|
||||
MOCKTAIL_VK_PROC(vkQueuePresentKHR)
|
||||
MOCKTAIL_VK_PROC(vkGetPhysicalDeviceFeatures)
|
||||
MOCKTAIL_VK_PROC(vkGetPhysicalDeviceFeatures2)
|
||||
MOCKTAIL_VK_PROC(vkGetPhysicalDeviceFormatProperties)
|
||||
MOCKTAIL_VK_PROC(vkGetPhysicalDeviceFormatProperties2)
|
||||
MOCKTAIL_VK_PROC(vkGetPhysicalDeviceImageFormatProperties)
|
||||
MOCKTAIL_VK_PROC(vkGetPhysicalDeviceImageFormatProperties2)
|
||||
MOCKTAIL_VK_PROC(vkCreateImage)
|
||||
MOCKTAIL_VK_PROC(vkDestroyImage)
|
||||
MOCKTAIL_VK_PROC(vkCreateImageView)
|
||||
MOCKTAIL_VK_PROC(vkBindBufferMemory)
|
||||
MOCKTAIL_VK_PROC(vkBindBufferMemory2)
|
||||
MOCKTAIL_VK_PROC(vkMapMemory)
|
||||
MOCKTAIL_VK_PROC(vkMapMemory2)
|
||||
MOCKTAIL_VK_PROC(vkUnmapMemory)
|
||||
MOCKTAIL_VK_PROC(vkUnmapMemory2)
|
||||
MOCKTAIL_VK_PROC(vkFreeMemory)
|
||||
MOCKTAIL_VK_PROC(vkDestroyBuffer)
|
||||
MOCKTAIL_VK_PROC(vkCmdCopyBufferToImage)
|
||||
MOCKTAIL_VK_PROC(vkCmdCopyImage)
|
||||
MOCKTAIL_VK_PROC(vkCmdCopyImage2)
|
||||
MOCKTAIL_VK_PROC(vkCmdCopyBufferToImage2)
|
||||
MOCKTAIL_VK_PROC(vkCmdExecuteCommands)
|
||||
#undef MOCKTAIL_VK_PROC
|
||||
#define MOCKTAIL_VK_ALIAS(alias, function) \
|
||||
if (std::strcmp(name, #alias) == 0) { \
|
||||
return reinterpret_cast<PFN_vkVoidFunction>(function); \
|
||||
}
|
||||
MOCKTAIL_VK_ALIAS(vkGetPhysicalDeviceFeatures2KHR,
|
||||
vkGetPhysicalDeviceFeatures2)
|
||||
MOCKTAIL_VK_ALIAS(vkGetPhysicalDeviceFormatProperties2KHR,
|
||||
vkGetPhysicalDeviceFormatProperties2)
|
||||
MOCKTAIL_VK_ALIAS(vkGetPhysicalDeviceImageFormatProperties2KHR,
|
||||
vkGetPhysicalDeviceImageFormatProperties2)
|
||||
MOCKTAIL_VK_ALIAS(vkBindBufferMemory2KHR, vkBindBufferMemory2)
|
||||
MOCKTAIL_VK_ALIAS(vkCmdCopyImage2KHR, vkCmdCopyImage2)
|
||||
MOCKTAIL_VK_ALIAS(vkMapMemory2KHR, vkMapMemory2)
|
||||
MOCKTAIL_VK_ALIAS(vkUnmapMemory2KHR, vkUnmapMemory2)
|
||||
MOCKTAIL_VK_ALIAS(vkCmdCopyBufferToImage2KHR, vkCmdCopyBufferToImage2)
|
||||
#undef MOCKTAIL_VK_ALIAS
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool NameInList(const char* name, const char* const* names,
|
||||
std::size_t count) {
|
||||
return name != nullptr &&
|
||||
std::any_of(names, names + count, [name](const char* candidate) {
|
||||
return std::strcmp(name, candidate) == 0;
|
||||
});
|
||||
}
|
||||
|
||||
bool IsEtc2DeviceProc(const char* name) {
|
||||
static constexpr const char* kNames[] = {
|
||||
"vkCreateImage", "vkDestroyImage",
|
||||
"vkCreateImageView", "vkBindBufferMemory",
|
||||
"vkBindBufferMemory2", "vkBindBufferMemory2KHR",
|
||||
"vkMapMemory", "vkMapMemory2",
|
||||
"vkMapMemory2KHR", "vkUnmapMemory",
|
||||
"vkUnmapMemory2", "vkUnmapMemory2KHR",
|
||||
"vkFreeMemory", "vkDestroyBuffer",
|
||||
"vkCmdCopyBufferToImage", "vkCmdCopyBufferToImage2",
|
||||
"vkCmdCopyImage", "vkCmdCopyImage2",
|
||||
"vkCmdCopyImage2KHR",
|
||||
"vkCmdCopyBufferToImage2KHR", "vkCmdExecuteCommands",
|
||||
};
|
||||
return NameInList(name, kNames, sizeof(kNames) / sizeof(kNames[0]));
|
||||
}
|
||||
|
||||
bool IsEtc2PhysicalDeviceProc(const char* name) {
|
||||
static constexpr const char* kNames[] = {
|
||||
"vkGetPhysicalDeviceFeatures",
|
||||
"vkGetPhysicalDeviceFeatures2",
|
||||
"vkGetPhysicalDeviceFeatures2KHR",
|
||||
"vkGetPhysicalDeviceFormatProperties",
|
||||
"vkGetPhysicalDeviceFormatProperties2",
|
||||
"vkGetPhysicalDeviceFormatProperties2KHR",
|
||||
"vkGetPhysicalDeviceImageFormatProperties",
|
||||
"vkGetPhysicalDeviceImageFormatProperties2",
|
||||
"vkGetPhysicalDeviceImageFormatProperties2KHR",
|
||||
};
|
||||
return NameInList(name, kNames, sizeof(kNames) / sizeof(kNames[0]));
|
||||
}
|
||||
|
||||
bool IsDeviceAdapterProc(const char* name) {
|
||||
return name != nullptr && (std::strcmp(name, "vkDestroyDevice") == 0 ||
|
||||
return name != nullptr && (IsEtc2DeviceProc(name) || std::strcmp(name, "vkDestroyDevice") == 0 ||
|
||||
std::strcmp(name, "vkGetDeviceQueue") == 0 ||
|
||||
std::strcmp(name, "vkGetDeviceQueue2") == 0 ||
|
||||
std::strcmp(name, "vkCreateSwapchainKHR") == 0 ||
|
||||
@@ -959,11 +1040,23 @@ struct FpsWaitTrace {
|
||||
std::atomic<std::uint64_t> total_ns{0};
|
||||
std::atomic<std::uint64_t> max_ns{0};
|
||||
std::atomic<std::uint64_t> window_start_ns{0};
|
||||
std::atomic<std::uint64_t> max_gap_ns{0};
|
||||
std::atomic<std::uint64_t> last_start_ns{0};
|
||||
|
||||
void Record(std::uint64_t start_ns) {
|
||||
if (name == nullptr || start_ns == 0) {
|
||||
return;
|
||||
}
|
||||
const std::uint64_t previous_start =
|
||||
last_start_ns.exchange(start_ns, std::memory_order_relaxed);
|
||||
if (previous_start != 0 && start_ns > previous_start) {
|
||||
const std::uint64_t gap_ns = start_ns - previous_start;
|
||||
std::uint64_t max_gap = max_gap_ns.load(std::memory_order_relaxed);
|
||||
while (gap_ns > max_gap &&
|
||||
!max_gap_ns.compare_exchange_weak(max_gap, gap_ns,
|
||||
std::memory_order_relaxed)) {
|
||||
}
|
||||
}
|
||||
const std::uint64_t wait_ns = MonotonicNanos() - start_ns;
|
||||
total_ns.fetch_add(wait_ns, std::memory_order_relaxed);
|
||||
std::uint64_t max_wait = max_ns.load(std::memory_order_relaxed);
|
||||
@@ -984,14 +1077,18 @@ struct FpsWaitTrace {
|
||||
const std::uint64_t total = total_ns.exchange(0, std::memory_order_relaxed);
|
||||
const std::uint64_t peak = max_ns.exchange(0, std::memory_order_relaxed);
|
||||
const std::uint64_t count = samples.exchange(0, std::memory_order_relaxed);
|
||||
const std::uint64_t gap_peak =
|
||||
max_gap_ns.exchange(0, std::memory_order_relaxed);
|
||||
window_start_ns.store(start_ns, std::memory_order_relaxed);
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
std::fprintf(stderr, " [fps] %s n=%llu avg=%llu us max=%llu us\n", name,
|
||||
static_cast<unsigned long long>(count),
|
||||
std::fprintf(stderr,
|
||||
" [fps] %s n=%llu avg=%llu us max=%llu us gap_max=%llu us\n",
|
||||
name, static_cast<unsigned long long>(count),
|
||||
static_cast<unsigned long long>(total / count / 1000ULL),
|
||||
static_cast<unsigned long long>(peak / 1000ULL));
|
||||
static_cast<unsigned long long>(peak / 1000ULL),
|
||||
static_cast<unsigned long long>(gap_peak / 1000ULL));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1042,6 +1139,58 @@ ObservedHostQueuePresent(VkQueue queue, const VkPresentInfoKHR* present_info) {
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
Function HostPhysicalDeviceProc(const char* name, const char* alias) {
|
||||
VkInstance instance = VK_NULL_HANDLE;
|
||||
{
|
||||
AdapterState& state = State();
|
||||
std::lock_guard<std::mutex> lock(state.mutex);
|
||||
instance = state.latest_instance;
|
||||
}
|
||||
PFN_vkVoidFunction proc = HostInstanceProc(instance, name);
|
||||
if (proc == nullptr && alias != nullptr) {
|
||||
proc = HostInstanceProc(instance, alias);
|
||||
}
|
||||
return reinterpret_cast<Function>(proc);
|
||||
}
|
||||
|
||||
bool Etc2EmulatedPhysicalDevice(VkPhysicalDevice physical_device) {
|
||||
return State().etc2.PhysicalDeviceNeedsEmulation(
|
||||
physical_device, HostPhysicalDeviceProc<PFN_vkGetPhysicalDeviceFeatures>(
|
||||
"vkGetPhysicalDeviceFeatures", nullptr));
|
||||
}
|
||||
|
||||
void ReleaseEtc2PoolCommandBuffers(VkDevice device,
|
||||
VkCommandPool command_pool) {
|
||||
std::vector<VkCommandBuffer> command_buffers;
|
||||
{
|
||||
AdapterState& state = State();
|
||||
std::lock_guard<std::mutex> lock(state.mutex);
|
||||
for (const auto& binding : state.command_buffer_bindings) {
|
||||
if (binding.device == device && binding.command_pool == command_pool) {
|
||||
command_buffers.push_back(binding.command_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (VkCommandBuffer command_buffer : command_buffers) {
|
||||
State().etc2.ReleaseCommandBuffer(command_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void PrepareEtc2Submit2(std::uint32_t submit_count,
|
||||
const VkSubmitInfo2* submits) {
|
||||
if (submits == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (std::uint32_t index = 0; index < submit_count; ++index) {
|
||||
for (std::uint32_t info = 0; info < submits[index].commandBufferInfoCount;
|
||||
++info) {
|
||||
State().etc2.PrepareSubmit(
|
||||
&submits[index].pCommandBufferInfos[info].commandBuffer, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
@@ -1247,7 +1396,7 @@ vkGetInstanceProcAddr(VkInstance instance, const char* name) {
|
||||
}
|
||||
if (const PFN_vkVoidFunction adapter = AdapterProc(name);
|
||||
adapter != nullptr) {
|
||||
if (IsDeviceAdapterProc(name) &&
|
||||
if ((IsDeviceAdapterProc(name) || IsEtc2PhysicalDeviceProc(name)) &&
|
||||
HostInstanceProc(instance, name) == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1306,6 +1455,30 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(
|
||||
|
||||
VkDeviceCreateInfo host_info = *create_info;
|
||||
EnabledDeviceFeatures enabled = InspectEnabledFeatures(*create_info);
|
||||
const bool etc2_emulated = State().etc2.PhysicalDeviceNeedsEmulation(
|
||||
physical_device, reinterpret_cast<PFN_vkGetPhysicalDeviceFeatures>(
|
||||
HostInstanceProc(instance,
|
||||
"vkGetPhysicalDeviceFeatures")));
|
||||
VkPhysicalDeviceFeatures host_enabled_features{};
|
||||
VkPhysicalDeviceFeatures2* requested_features2 = nullptr;
|
||||
VkBool32 requested_features2_etc2 = VK_FALSE;
|
||||
if (etc2_emulated) {
|
||||
enabled.root.features.textureCompressionETC2 = VK_FALSE;
|
||||
if (create_info->pEnabledFeatures != nullptr) {
|
||||
host_enabled_features = *create_info->pEnabledFeatures;
|
||||
host_enabled_features.textureCompressionETC2 = VK_FALSE;
|
||||
host_info.pEnabledFeatures = &host_enabled_features;
|
||||
}
|
||||
// Restore the caller-owned feature bit after the host call.
|
||||
requested_features2 = const_cast<VkPhysicalDeviceFeatures2*>(
|
||||
reinterpret_cast<const VkPhysicalDeviceFeatures2*>(FindFeature(
|
||||
create_info->pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2)));
|
||||
if (requested_features2 != nullptr) {
|
||||
requested_features2_etc2 =
|
||||
requested_features2->features.textureCompressionETC2;
|
||||
requested_features2->features.textureCompressionETC2 = VK_FALSE;
|
||||
}
|
||||
}
|
||||
enabled.root.pNext = &enabled.vulkan11;
|
||||
enabled.vulkan11.pNext = &enabled.vulkan12;
|
||||
enabled.vulkan12.pNext = nullptr;
|
||||
@@ -1341,11 +1514,25 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(
|
||||
|
||||
const VkResult result =
|
||||
host_create(physical_device, &host_info, allocator, device);
|
||||
if (requested_features2 != nullptr) {
|
||||
requested_features2->features.textureCompressionETC2 =
|
||||
requested_features2_etc2;
|
||||
}
|
||||
if (result != VK_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
RegisterHostDeviceDispatch(*device, physical_device,
|
||||
host_get_device_proc_addr);
|
||||
VkPhysicalDeviceMemoryProperties memory_properties{};
|
||||
if (const auto host_memory_properties =
|
||||
reinterpret_cast<PFN_vkGetPhysicalDeviceMemoryProperties>(
|
||||
HostInstanceProc(instance,
|
||||
"vkGetPhysicalDeviceMemoryProperties"));
|
||||
host_memory_properties != nullptr) {
|
||||
host_memory_properties(physical_device, &memory_properties);
|
||||
}
|
||||
State().etc2.RegisterDevice(*device, physical_device, etc2_emulated,
|
||||
memory_properties, host_get_device_proc_addr);
|
||||
|
||||
std::uint32_t queue_family_count = 0;
|
||||
const auto host_get_queue_families =
|
||||
@@ -1403,6 +1590,7 @@ vkDestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator) {
|
||||
const auto host_destroy = reinterpret_cast<PFN_vkDestroyDevice>(
|
||||
HostDeviceProc(device, "vkDestroyDevice"));
|
||||
State().text_overlay.DestroyDevice(device);
|
||||
State().etc2.DestroyDevice(device);
|
||||
if (host_destroy != nullptr) {
|
||||
host_destroy(device, allocator);
|
||||
}
|
||||
@@ -1650,6 +1838,7 @@ VKAPI_ATTR VkResult VKAPI_CALL vkResetCommandPool(
|
||||
observation.SetResult(VK_ERROR_INITIALIZATION_FAILED);
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
ReleaseEtc2PoolCommandBuffers(device, command_pool);
|
||||
const VkResult result = host_reset(device, command_pool, flags);
|
||||
observation.SetResult(result);
|
||||
return result;
|
||||
@@ -1706,6 +1895,11 @@ VKAPI_ATTR void VKAPI_CALL vkFreeCommandBuffers(
|
||||
observation.SetResult(VK_ERROR_INITIALIZATION_FAILED);
|
||||
return;
|
||||
}
|
||||
if (command_buffers != nullptr) {
|
||||
for (std::uint32_t index = 0; index < command_buffer_count; ++index) {
|
||||
State().etc2.ReleaseCommandBuffer(command_buffers[index]);
|
||||
}
|
||||
}
|
||||
host_free(device, command_pool, command_buffer_count, command_buffers);
|
||||
RemoveHostCommandBuffers(device, command_pool, command_buffer_count,
|
||||
command_buffers);
|
||||
@@ -1722,6 +1916,7 @@ VKAPI_ATTR void VKAPI_CALL vkDestroyCommandPool(
|
||||
observation.SetResult(VK_ERROR_INITIALIZATION_FAILED);
|
||||
return;
|
||||
}
|
||||
ReleaseEtc2PoolCommandBuffers(device, command_pool);
|
||||
host_destroy(device, command_pool, allocator);
|
||||
RemoveHostCommandPoolBindings(device, command_pool);
|
||||
observation.SetResult(VK_SUCCESS);
|
||||
@@ -1737,6 +1932,7 @@ VKAPI_ATTR VkResult VKAPI_CALL vkBeginCommandBuffer(
|
||||
observation.SetResult(VK_ERROR_INITIALIZATION_FAILED);
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
State().etc2.ReleaseCommandBuffer(command_buffer);
|
||||
const VkResult result = host_begin(command_buffer, begin_info);
|
||||
observation.SetResult(result);
|
||||
return result;
|
||||
@@ -1765,6 +1961,7 @@ VKAPI_ATTR VkResult VKAPI_CALL vkResetCommandBuffer(
|
||||
observation.SetResult(VK_ERROR_INITIALIZATION_FAILED);
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
State().etc2.ReleaseCommandBuffer(command_buffer);
|
||||
const VkResult result = host_reset(command_buffer, flags);
|
||||
observation.SetResult(result);
|
||||
return result;
|
||||
@@ -1801,6 +1998,12 @@ VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit(
|
||||
observation.SetResult(VK_ERROR_INITIALIZATION_FAILED);
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
if (submits != nullptr) {
|
||||
for (std::uint32_t index = 0; index < submit_count; ++index) {
|
||||
State().etc2.PrepareSubmit(submits[index].pCommandBuffers,
|
||||
submits[index].commandBufferCount);
|
||||
}
|
||||
}
|
||||
const VkResult result = State().text_overlay.QueueSubmit(
|
||||
queue, submit_count, submits, fence, host_submit);
|
||||
observation.SetResult(result);
|
||||
@@ -1817,6 +2020,7 @@ VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit2(
|
||||
observation.SetResult(VK_ERROR_INITIALIZATION_FAILED);
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
PrepareEtc2Submit2(submit_count, submits);
|
||||
const VkResult result = State().text_overlay.QueueSubmit2(
|
||||
queue, submit_count, submits, fence, host_submit);
|
||||
observation.SetResult(result);
|
||||
@@ -1833,6 +2037,7 @@ VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit2KHR(
|
||||
observation.SetResult(VK_ERROR_INITIALIZATION_FAILED);
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
PrepareEtc2Submit2(submit_count, submits);
|
||||
const VkResult result = State().text_overlay.QueueSubmit2(
|
||||
queue, submit_count, submits, fence,
|
||||
reinterpret_cast<PFN_vkQueueSubmit2>(host_submit));
|
||||
@@ -2025,4 +2230,250 @@ vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) {
|
||||
return normalized_result;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFeatures(
|
||||
VkPhysicalDevice physical_device, VkPhysicalDeviceFeatures* features) {
|
||||
const auto host = HostPhysicalDeviceProc<PFN_vkGetPhysicalDeviceFeatures>(
|
||||
"vkGetPhysicalDeviceFeatures", nullptr);
|
||||
if (host == nullptr || features == nullptr) {
|
||||
return;
|
||||
}
|
||||
host(physical_device, features);
|
||||
if (Etc2EmulatedPhysicalDevice(physical_device) &&
|
||||
mocktail::graphics::Etc2SupportAdvertised()) {
|
||||
features->textureCompressionETC2 = VK_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFeatures2(
|
||||
VkPhysicalDevice physical_device, VkPhysicalDeviceFeatures2* features) {
|
||||
const auto host = HostPhysicalDeviceProc<PFN_vkGetPhysicalDeviceFeatures2>(
|
||||
"vkGetPhysicalDeviceFeatures2", "vkGetPhysicalDeviceFeatures2KHR");
|
||||
if (host == nullptr || features == nullptr) {
|
||||
return;
|
||||
}
|
||||
host(physical_device, features);
|
||||
if (Etc2EmulatedPhysicalDevice(physical_device) &&
|
||||
mocktail::graphics::Etc2SupportAdvertised()) {
|
||||
features->features.textureCompressionETC2 = VK_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFormatProperties(
|
||||
VkPhysicalDevice physical_device, VkFormat format,
|
||||
VkFormatProperties* properties) {
|
||||
const auto host =
|
||||
HostPhysicalDeviceProc<PFN_vkGetPhysicalDeviceFormatProperties>(
|
||||
"vkGetPhysicalDeviceFormatProperties", nullptr);
|
||||
if (host == nullptr || properties == nullptr) {
|
||||
return;
|
||||
}
|
||||
mocktail::graphics::Etc2EmulatedFormat mapped;
|
||||
if (!mocktail::graphics::LookupEmulatedEtc2Format(format, &mapped) ||
|
||||
!Etc2EmulatedPhysicalDevice(physical_device)) {
|
||||
host(physical_device, format, properties);
|
||||
return;
|
||||
}
|
||||
host(physical_device, mapped.host_format, properties);
|
||||
properties->linearTilingFeatures = 0;
|
||||
properties->optimalTilingFeatures =
|
||||
mocktail::graphics::EmulatedEtc2FormatFeatures(
|
||||
properties->optimalTilingFeatures);
|
||||
properties->bufferFeatures = 0;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFormatProperties2(
|
||||
VkPhysicalDevice physical_device, VkFormat format,
|
||||
VkFormatProperties2* properties) {
|
||||
const auto host =
|
||||
HostPhysicalDeviceProc<PFN_vkGetPhysicalDeviceFormatProperties2>(
|
||||
"vkGetPhysicalDeviceFormatProperties2",
|
||||
"vkGetPhysicalDeviceFormatProperties2KHR");
|
||||
if (host == nullptr || properties == nullptr) {
|
||||
return;
|
||||
}
|
||||
mocktail::graphics::Etc2EmulatedFormat mapped;
|
||||
if (!mocktail::graphics::LookupEmulatedEtc2Format(format, &mapped) ||
|
||||
!Etc2EmulatedPhysicalDevice(physical_device)) {
|
||||
host(physical_device, format, properties);
|
||||
return;
|
||||
}
|
||||
host(physical_device, mapped.host_format, properties);
|
||||
VkFormatProperties& base = properties->formatProperties;
|
||||
base.linearTilingFeatures = 0;
|
||||
base.optimalTilingFeatures =
|
||||
mocktail::graphics::EmulatedEtc2FormatFeatures(base.optimalTilingFeatures);
|
||||
base.bufferFeatures = 0;
|
||||
if (auto* extended = const_cast<VkFormatProperties3*>(
|
||||
reinterpret_cast<const VkFormatProperties3*>(FindFeature(
|
||||
properties->pNext, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3)));
|
||||
extended != nullptr) {
|
||||
extended->linearTilingFeatures = 0;
|
||||
extended->optimalTilingFeatures =
|
||||
mocktail::graphics::EmulatedEtc2FormatFeatures(
|
||||
static_cast<VkFormatFeatureFlags>(
|
||||
extended->optimalTilingFeatures));
|
||||
extended->bufferFeatures = 0;
|
||||
}
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceImageFormatProperties(
|
||||
VkPhysicalDevice physical_device, VkFormat format, VkImageType type,
|
||||
VkImageTiling tiling, VkImageUsageFlags usage, VkImageCreateFlags flags,
|
||||
VkImageFormatProperties* properties) {
|
||||
const auto host =
|
||||
HostPhysicalDeviceProc<PFN_vkGetPhysicalDeviceImageFormatProperties>(
|
||||
"vkGetPhysicalDeviceImageFormatProperties", nullptr);
|
||||
if (host == nullptr) {
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
mocktail::graphics::Etc2EmulatedFormat mapped;
|
||||
if (!mocktail::graphics::LookupEmulatedEtc2Format(format, &mapped) ||
|
||||
!Etc2EmulatedPhysicalDevice(physical_device)) {
|
||||
return host(physical_device, format, type, tiling, usage, flags,
|
||||
properties);
|
||||
}
|
||||
if (tiling != VK_IMAGE_TILING_OPTIMAL || type != VK_IMAGE_TYPE_2D) {
|
||||
return VK_ERROR_FORMAT_NOT_SUPPORTED;
|
||||
}
|
||||
return host(physical_device, mapped.host_format, type, tiling, usage,
|
||||
flags & ~static_cast<VkImageCreateFlags>(
|
||||
VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT),
|
||||
properties);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceImageFormatProperties2(
|
||||
VkPhysicalDevice physical_device,
|
||||
const VkPhysicalDeviceImageFormatInfo2* info,
|
||||
VkImageFormatProperties2* properties) {
|
||||
const auto host =
|
||||
HostPhysicalDeviceProc<PFN_vkGetPhysicalDeviceImageFormatProperties2>(
|
||||
"vkGetPhysicalDeviceImageFormatProperties2",
|
||||
"vkGetPhysicalDeviceImageFormatProperties2KHR");
|
||||
if (host == nullptr || info == nullptr) {
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
mocktail::graphics::Etc2EmulatedFormat mapped;
|
||||
if (!mocktail::graphics::LookupEmulatedEtc2Format(info->format, &mapped) ||
|
||||
!Etc2EmulatedPhysicalDevice(physical_device)) {
|
||||
return host(physical_device, info, properties);
|
||||
}
|
||||
if (info->tiling != VK_IMAGE_TILING_OPTIMAL ||
|
||||
info->type != VK_IMAGE_TYPE_2D) {
|
||||
return VK_ERROR_FORMAT_NOT_SUPPORTED;
|
||||
}
|
||||
VkPhysicalDeviceImageFormatInfo2 host_info = *info;
|
||||
host_info.format = mapped.host_format;
|
||||
host_info.flags &= ~static_cast<VkImageCreateFlags>(
|
||||
VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT);
|
||||
return host(physical_device, &host_info, properties);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkCreateImage(
|
||||
VkDevice device, const VkImageCreateInfo* create_info,
|
||||
const VkAllocationCallbacks* allocator, VkImage* image) {
|
||||
return State().etc2.CreateImage(device, create_info, allocator, image);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkDestroyImage(
|
||||
VkDevice device, VkImage image, const VkAllocationCallbacks* allocator) {
|
||||
State().etc2.DestroyImage(device, image, allocator);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkCreateImageView(
|
||||
VkDevice device, const VkImageViewCreateInfo* create_info,
|
||||
const VkAllocationCallbacks* allocator, VkImageView* view) {
|
||||
return State().etc2.CreateImageView(device, create_info, allocator, view);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory(VkDevice device,
|
||||
VkBuffer buffer,
|
||||
VkDeviceMemory memory,
|
||||
VkDeviceSize offset) {
|
||||
return State().etc2.BindBufferMemory(device, buffer, memory, offset);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory2(
|
||||
VkDevice device, std::uint32_t count,
|
||||
const VkBindBufferMemoryInfo* infos) {
|
||||
return State().etc2.BindBufferMemory2(device, count, infos);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkMapMemory(VkDevice device,
|
||||
VkDeviceMemory memory,
|
||||
VkDeviceSize offset,
|
||||
VkDeviceSize size,
|
||||
VkMemoryMapFlags flags,
|
||||
void** data) {
|
||||
return State().etc2.MapMemory(device, memory, offset, size, flags, data);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkMapMemory2(VkDevice device,
|
||||
const VkMemoryMapInfo* info,
|
||||
void** data) {
|
||||
return State().etc2.MapMemory2(device, info, data);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkUnmapMemory(VkDevice device,
|
||||
VkDeviceMemory memory) {
|
||||
State().etc2.UnmapMemory(device, memory);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkUnmapMemory2(VkDevice device,
|
||||
const VkMemoryUnmapInfo* info) {
|
||||
return State().etc2.UnmapMemory2(device, info);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkFreeMemory(
|
||||
VkDevice device, VkDeviceMemory memory,
|
||||
const VkAllocationCallbacks* allocator) {
|
||||
State().etc2.FreeMemory(device, memory, allocator);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkDestroyBuffer(
|
||||
VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* allocator) {
|
||||
State().etc2.DestroyBuffer(device, buffer, allocator);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkCmdCopyBufferToImage(
|
||||
VkCommandBuffer command_buffer, VkBuffer source, VkImage destination,
|
||||
VkImageLayout layout, std::uint32_t region_count,
|
||||
const VkBufferImageCopy* regions) {
|
||||
State().etc2.CmdCopyBufferToImage(
|
||||
HostDispatchForCommandBuffer(command_buffer).device, command_buffer,
|
||||
source, destination, layout, region_count, regions);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkCmdCopyBufferToImage2(
|
||||
VkCommandBuffer command_buffer, const VkCopyBufferToImageInfo2* info) {
|
||||
State().etc2.CmdCopyBufferToImage2(
|
||||
HostDispatchForCommandBuffer(command_buffer).device, command_buffer,
|
||||
info);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkCmdCopyImage(
|
||||
VkCommandBuffer command_buffer, VkImage source,
|
||||
VkImageLayout source_layout, VkImage destination,
|
||||
VkImageLayout destination_layout, std::uint32_t region_count,
|
||||
const VkImageCopy* regions) {
|
||||
State().etc2.CmdCopyImage(
|
||||
HostDispatchForCommandBuffer(command_buffer).device, command_buffer,
|
||||
source, source_layout, destination, destination_layout, region_count,
|
||||
regions);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkCmdCopyImage2(VkCommandBuffer command_buffer,
|
||||
const VkCopyImageInfo2* info) {
|
||||
State().etc2.CmdCopyImage2(
|
||||
HostDispatchForCommandBuffer(command_buffer).device, command_buffer,
|
||||
info);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkCmdExecuteCommands(
|
||||
VkCommandBuffer command_buffer, std::uint32_t count,
|
||||
const VkCommandBuffer* secondaries) {
|
||||
State().etc2.CmdExecuteCommands(
|
||||
HostDispatchForCommandBuffer(command_buffer).device, command_buffer,
|
||||
count, secondaries);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,398 @@
|
||||
#include "mocktail/graphics/etc2_decoder.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
namespace mocktail::graphics {
|
||||
namespace {
|
||||
|
||||
constexpr std::array<int, 8> kDistance = {3, 6, 11, 16, 23, 32, 41, 64};
|
||||
|
||||
// Indexed by (msb << 1) | lsb.
|
||||
constexpr int kEtc1Modifiers[8][4] = {
|
||||
{2, 8, -2, -8}, {5, 17, -5, -17}, {9, 29, -9, -29},
|
||||
{13, 42, -13, -42}, {18, 60, -18, -60}, {24, 80, -24, -80},
|
||||
{33, 106, -33, -106}, {47, 183, -47, -183},
|
||||
};
|
||||
|
||||
constexpr int kEacModifiers[16][8] = {
|
||||
{-3, -6, -9, -15, 2, 5, 8, 14}, {-3, -7, -10, -13, 2, 6, 9, 12},
|
||||
{-2, -5, -8, -13, 1, 4, 7, 12}, {-2, -4, -6, -13, 1, 3, 5, 12},
|
||||
{-3, -6, -8, -12, 2, 5, 7, 11}, {-3, -7, -9, -11, 2, 6, 8, 10},
|
||||
{-4, -7, -8, -11, 3, 6, 7, 10}, {-3, -5, -8, -11, 2, 4, 7, 10},
|
||||
{-2, -6, -8, -10, 1, 5, 7, 9}, {-2, -5, -8, -10, 1, 4, 7, 9},
|
||||
{-2, -4, -8, -10, 1, 3, 7, 9}, {-2, -5, -7, -10, 1, 4, 6, 9},
|
||||
{-3, -4, -7, -10, 2, 3, 6, 9}, {-1, -2, -3, -10, 0, 1, 2, 9},
|
||||
{-4, -6, -8, -9, 3, 5, 7, 8}, {-3, -5, -7, -9, 2, 4, 6, 8},
|
||||
};
|
||||
|
||||
std::uint8_t Clamp255(int value) {
|
||||
return static_cast<std::uint8_t>(std::clamp(value, 0, 255));
|
||||
}
|
||||
|
||||
int Expand4(int value) { return (value << 4) | value; }
|
||||
int Expand5(int value) { return (value << 3) | (value >> 2); }
|
||||
int Expand6(int value) { return (value << 2) | (value >> 4); }
|
||||
int Expand7(int value) { return (value << 1) | (value >> 6); }
|
||||
|
||||
// Texels use column-major indexing: x * 4 + y.
|
||||
void DecodeRgbBlock(const std::uint8_t* b, bool punchthrough,
|
||||
std::array<std::uint8_t, 64>* out) {
|
||||
static constexpr int kDelta[8] = {0, 1, 2, 3, -4, -3, -2, -1};
|
||||
const bool diff_or_opaque = (b[3] & 2) != 0;
|
||||
const bool opaque = !punchthrough || diff_or_opaque;
|
||||
const int msb = (b[4] << 8) | b[5];
|
||||
const int lsb = (b[6] << 8) | b[7];
|
||||
auto index_of = [msb, lsb](int texel) {
|
||||
return (((msb >> texel) & 1) << 1) | ((lsb >> texel) & 1);
|
||||
};
|
||||
auto write = [out](int texel, int r, int g, int bl, int a) {
|
||||
const std::size_t o = static_cast<std::size_t>(texel) * 4;
|
||||
(*out)[o] = Clamp255(r);
|
||||
(*out)[o + 1] = Clamp255(g);
|
||||
(*out)[o + 2] = Clamp255(bl);
|
||||
(*out)[o + 3] = static_cast<std::uint8_t>(a);
|
||||
};
|
||||
|
||||
const int r_sum = (b[0] >> 3) + kDelta[b[0] & 7];
|
||||
const int g_sum = (b[1] >> 3) + kDelta[b[1] & 7];
|
||||
const int b_sum = (b[2] >> 3) + kDelta[b[2] & 7];
|
||||
const bool individual = !punchthrough && !diff_or_opaque;
|
||||
|
||||
if (!individual && (r_sum < 0 || r_sum > 31 || g_sum < 0 || g_sum > 31)) {
|
||||
std::array<std::array<int, 3>, 4> paint{};
|
||||
if (r_sum < 0 || r_sum > 31) {
|
||||
const std::array<int, 3> c1 = {
|
||||
Expand4((((b[0] >> 3) & 3) << 2) | (b[0] & 3)),
|
||||
Expand4(b[1] >> 4), Expand4(b[1] & 15)};
|
||||
const std::array<int, 3> c2 = {Expand4(b[2] >> 4), Expand4(b[2] & 15),
|
||||
Expand4(b[3] >> 4)};
|
||||
const int d = kDistance[(((b[3] >> 2) & 3) << 1) | (b[3] & 1)];
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
paint[0][c] = c1[c];
|
||||
paint[1][c] = c2[c] + d;
|
||||
paint[2][c] = c2[c];
|
||||
paint[3][c] = c2[c] - d;
|
||||
}
|
||||
} else {
|
||||
const std::array<int, 3> c1 = {
|
||||
Expand4((b[0] >> 3) & 15),
|
||||
Expand4(((b[0] & 7) << 1) | ((b[1] >> 4) & 1)),
|
||||
Expand4((b[1] & 8) | ((b[1] & 3) << 1) | (b[2] >> 7))};
|
||||
const std::array<int, 3> c2 = {
|
||||
Expand4((b[2] >> 3) & 15), Expand4(((b[2] & 7) << 1) | (b[3] >> 7)),
|
||||
Expand4((b[3] >> 3) & 15)};
|
||||
const int v1 = (c1[0] << 16) | (c1[1] << 8) | c1[2];
|
||||
const int v2 = (c2[0] << 16) | (c2[1] << 8) | c2[2];
|
||||
const int d =
|
||||
kDistance[(b[3] & 4) | ((b[3] & 1) << 1) | (v1 >= v2 ? 1 : 0)];
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
paint[0][c] = c1[c] + d;
|
||||
paint[1][c] = c1[c] - d;
|
||||
paint[2][c] = c2[c] + d;
|
||||
paint[3][c] = c2[c] - d;
|
||||
}
|
||||
}
|
||||
for (int texel = 0; texel < 16; ++texel) {
|
||||
const int index = index_of(texel);
|
||||
if (!opaque && index == 2) {
|
||||
write(texel, 0, 0, 0, 0);
|
||||
} else {
|
||||
write(texel, paint[index][0], paint[index][1], paint[index][2], 255);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!individual && (b_sum < 0 || b_sum > 31)) {
|
||||
const int ro = Expand6((b[0] >> 1) & 63);
|
||||
const int go = Expand7(((b[0] & 1) << 6) | ((b[1] >> 1) & 63));
|
||||
const int bo = Expand6(((b[1] & 1) << 5) | (b[2] & 0x18) |
|
||||
((b[2] & 3) << 1) | (b[3] >> 7));
|
||||
const int rh = Expand6(((b[3] & 0x7c) >> 1) | (b[3] & 1));
|
||||
const int gh = Expand7(b[4] >> 1);
|
||||
const int bh = Expand6(((b[4] & 1) << 5) | (b[5] >> 3));
|
||||
const int rv = Expand6(((b[5] & 7) << 3) | (b[6] >> 5));
|
||||
const int gv = Expand7(((b[6] & 31) << 2) | (b[7] >> 6));
|
||||
const int bv = Expand6(b[7] & 63);
|
||||
for (int x = 0; x < 4; ++x) {
|
||||
for (int y = 0; y < 4; ++y) {
|
||||
write(x * 4 + y, (x * (rh - ro) + y * (rv - ro) + 4 * ro + 2) >> 2,
|
||||
(x * (gh - go) + y * (gv - go) + 4 * go + 2) >> 2,
|
||||
(x * (bh - bo) + y * (bv - bo) + 4 * bo + 2) >> 2, 255);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::array<std::array<int, 3>, 2> base{};
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
if (individual) {
|
||||
base[0][c] = Expand4(b[c] >> 4);
|
||||
base[1][c] = Expand4(b[c] & 15);
|
||||
} else {
|
||||
base[0][c] = Expand5(b[c] >> 3);
|
||||
base[1][c] = Expand5((b[c] >> 3) + kDelta[b[c] & 7]);
|
||||
}
|
||||
}
|
||||
const int tables[2] = {b[3] >> 5, (b[3] >> 2) & 7};
|
||||
const bool flipped = (b[3] & 1) != 0;
|
||||
for (int x = 0; x < 4; ++x) {
|
||||
for (int y = 0; y < 4; ++y) {
|
||||
const int texel = x * 4 + y;
|
||||
const int index = index_of(texel);
|
||||
if (!opaque && index == 2) {
|
||||
write(texel, 0, 0, 0, 0);
|
||||
continue;
|
||||
}
|
||||
const int sub = flipped ? (y >= 2 ? 1 : 0) : (x >= 2 ? 1 : 0);
|
||||
const int modifier =
|
||||
(!opaque && index == 0) ? 0 : kEtc1Modifiers[tables[sub]][index];
|
||||
write(texel, base[sub][0] + modifier, base[sub][1] + modifier,
|
||||
base[sub][2] + modifier, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int EacIndex(const std::uint8_t* b, int texel) {
|
||||
std::uint64_t bits = 0;
|
||||
for (int i = 2; i < 8; ++i) {
|
||||
bits = (bits << 8) | b[i];
|
||||
}
|
||||
return static_cast<int>((bits >> (45 - texel * 3)) & 7);
|
||||
}
|
||||
|
||||
int EacModifier(const std::uint8_t* b, int texel) {
|
||||
return kEacModifiers[b[1] & 15][EacIndex(b, texel)];
|
||||
}
|
||||
|
||||
std::uint16_t DecodeR11(const std::uint8_t* b, int texel, bool is_signed) {
|
||||
const int multiplier = b[1] >> 4;
|
||||
const int modifier = EacModifier(b, texel);
|
||||
const int scaled = multiplier != 0 ? modifier * multiplier * 8 : modifier;
|
||||
if (!is_signed) {
|
||||
const int value = std::clamp(b[0] * 8 + 4 + scaled, 0, 2047);
|
||||
return static_cast<std::uint16_t>((value << 5) | (value >> 6));
|
||||
}
|
||||
const int base = std::max(-127, static_cast<int>(static_cast<std::int8_t>(b[0])));
|
||||
const int value = std::clamp(base * 8 + scaled, -1023, 1023);
|
||||
const int magnitude = value < 0 ? -value : value;
|
||||
const int expanded = (magnitude << 5) | (magnitude >> 5);
|
||||
return static_cast<std::uint16_t>(
|
||||
static_cast<std::int16_t>(value < 0 ? -expanded : expanded));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::size_t EtcBlockBytes(EtcFormat format) {
|
||||
switch (format) {
|
||||
case EtcFormat::kEtc2Rgba8:
|
||||
case EtcFormat::kEacRg11:
|
||||
case EtcFormat::kEacRg11Signed:
|
||||
return 16;
|
||||
default:
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t EtcDecodedTexelBytes(EtcFormat format) {
|
||||
switch (format) {
|
||||
case EtcFormat::kEacR11:
|
||||
case EtcFormat::kEacR11Signed:
|
||||
return 2;
|
||||
default:
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::uint64_t kBlocksPerBand = 16384;
|
||||
constexpr std::uint64_t kParallelMinimumBlocks = 65536;
|
||||
|
||||
bool ImageFits(EtcFormat format, const std::uint8_t* source,
|
||||
std::size_t source_bytes, std::uint32_t width,
|
||||
std::uint32_t height, const std::uint8_t* destination,
|
||||
std::size_t destination_bytes) {
|
||||
if (source == nullptr || destination == nullptr || width == 0 ||
|
||||
height == 0) {
|
||||
return false;
|
||||
}
|
||||
const std::uint64_t blocks_wide = (static_cast<std::uint64_t>(width) + 3) / 4;
|
||||
const std::uint64_t blocks_high =
|
||||
(static_cast<std::uint64_t>(height) + 3) / 4;
|
||||
return blocks_wide * blocks_high * EtcBlockBytes(format) <= source_bytes &&
|
||||
static_cast<std::uint64_t>(width) * height *
|
||||
EtcDecodedTexelBytes(format) <=
|
||||
destination_bytes;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool DecodeEtcImage(EtcFormat format, const std::uint8_t* source,
|
||||
std::size_t source_bytes, std::uint32_t width,
|
||||
std::uint32_t height, std::uint8_t* destination,
|
||||
std::size_t destination_bytes) {
|
||||
return DecodeEtcImageBlockRows(format, source, source_bytes, width, height, 0,
|
||||
(height + 3) / 4, destination,
|
||||
destination_bytes);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct DecodeBand {
|
||||
EtcDecodeJob* job;
|
||||
std::uint32_t first_block_row;
|
||||
std::uint32_t block_row_count;
|
||||
};
|
||||
|
||||
struct DecodeBandQueue {
|
||||
std::vector<DecodeBand> bands;
|
||||
std::atomic<std::size_t> next{0};
|
||||
|
||||
void Run() {
|
||||
for (;;) {
|
||||
const std::size_t index = next.fetch_add(1, std::memory_order_relaxed);
|
||||
if (index >= bands.size()) {
|
||||
return;
|
||||
}
|
||||
const DecodeBand& band = bands[index];
|
||||
const EtcDecodeJob& job = *band.job;
|
||||
DecodeEtcImageBlockRows(job.format, job.source, job.source_bytes,
|
||||
job.width, job.height, band.first_block_row,
|
||||
band.block_row_count, job.destination,
|
||||
job.destination_bytes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void* RunDecodeBandQueue(void* queue) {
|
||||
static_cast<DecodeBandQueue*>(queue)->Run();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void DecodeEtcJobs(EtcDecodeJob* jobs, std::size_t count,
|
||||
unsigned worker_count) {
|
||||
if (jobs == nullptr) {
|
||||
return;
|
||||
}
|
||||
DecodeBandQueue queue;
|
||||
std::vector<DecodeBand>& bands = queue.bands;
|
||||
std::uint64_t total_blocks = 0;
|
||||
for (std::size_t index = 0; index < count; ++index) {
|
||||
EtcDecodeJob& job = jobs[index];
|
||||
job.ok = ImageFits(job.format, job.source, job.source_bytes, job.width,
|
||||
job.height, job.destination, job.destination_bytes);
|
||||
if (!job.ok) {
|
||||
continue;
|
||||
}
|
||||
const std::uint32_t blocks_wide = (job.width + 3) / 4;
|
||||
const std::uint32_t blocks_high = (job.height + 3) / 4;
|
||||
const std::uint32_t rows_per_band = static_cast<std::uint32_t>(
|
||||
std::max<std::uint64_t>(1, kBlocksPerBand / blocks_wide));
|
||||
for (std::uint32_t row = 0; row < blocks_high; row += rows_per_band) {
|
||||
bands.push_back(
|
||||
{&job, row, std::min(rows_per_band, blocks_high - row)});
|
||||
}
|
||||
total_blocks += static_cast<std::uint64_t>(blocks_wide) * blocks_high;
|
||||
}
|
||||
|
||||
const std::size_t threads =
|
||||
total_blocks < kParallelMinimumBlocks
|
||||
? 1
|
||||
: std::min<std::size_t>(std::max(worker_count, 1U), bands.size());
|
||||
std::vector<pthread_t> workers;
|
||||
workers.reserve(threads > 0 ? threads - 1 : 0);
|
||||
for (std::size_t index = 1; index < threads; ++index) {
|
||||
pthread_t worker;
|
||||
if (pthread_create(&worker, nullptr, RunDecodeBandQueue, &queue) != 0) {
|
||||
break;
|
||||
}
|
||||
workers.push_back(worker);
|
||||
}
|
||||
queue.Run();
|
||||
for (pthread_t worker : workers) {
|
||||
pthread_join(worker, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool DecodeEtcImageBlockRows(EtcFormat format, const std::uint8_t* source,
|
||||
std::size_t source_bytes, std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
std::uint32_t first_block_row,
|
||||
std::uint32_t block_row_count,
|
||||
std::uint8_t* destination,
|
||||
std::size_t destination_bytes) {
|
||||
if (!ImageFits(format, source, source_bytes, width, height, destination,
|
||||
destination_bytes)) {
|
||||
return false;
|
||||
}
|
||||
const std::uint64_t blocks_wide = (static_cast<std::uint64_t>(width) + 3) / 4;
|
||||
const std::uint64_t blocks_high =
|
||||
(static_cast<std::uint64_t>(height) + 3) / 4;
|
||||
if (static_cast<std::uint64_t>(first_block_row) + block_row_count >
|
||||
blocks_high) {
|
||||
return false;
|
||||
}
|
||||
const std::size_t texel_bytes = EtcDecodedTexelBytes(format);
|
||||
const std::size_t block_bytes = EtcBlockBytes(format);
|
||||
|
||||
std::array<std::uint8_t, 64> rgba{};
|
||||
const std::uint64_t end_block_row =
|
||||
static_cast<std::uint64_t>(first_block_row) + block_row_count;
|
||||
for (std::uint64_t by = first_block_row; by < end_block_row; ++by) {
|
||||
for (std::uint64_t bx = 0; bx < blocks_wide; ++bx) {
|
||||
const std::uint8_t* block =
|
||||
source + (by * blocks_wide + bx) * block_bytes;
|
||||
if (format == EtcFormat::kEtc2Rgb8 || format == EtcFormat::kEtc2Rgb8A1) {
|
||||
DecodeRgbBlock(block, format == EtcFormat::kEtc2Rgb8A1, &rgba);
|
||||
} else if (format == EtcFormat::kEtc2Rgba8) {
|
||||
DecodeRgbBlock(block + 8, false, &rgba);
|
||||
}
|
||||
for (std::uint32_t y = 0; y < 4 && by * 4 + y < height; ++y) {
|
||||
for (std::uint32_t x = 0; x < 4 && bx * 4 + x < width; ++x) {
|
||||
const int texel = static_cast<int>(x * 4 + y);
|
||||
std::uint8_t* out =
|
||||
destination + ((by * 4 + y) * width + bx * 4 + x) * texel_bytes;
|
||||
switch (format) {
|
||||
case EtcFormat::kEtc2Rgb8:
|
||||
case EtcFormat::kEtc2Rgb8A1:
|
||||
case EtcFormat::kEtc2Rgba8:
|
||||
std::copy_n(rgba.data() + texel * 4, 4, out);
|
||||
if (format == EtcFormat::kEtc2Rgba8) {
|
||||
const int alpha =
|
||||
block[0] + EacModifier(block, texel) * (block[1] >> 4);
|
||||
out[3] = Clamp255(alpha);
|
||||
}
|
||||
break;
|
||||
case EtcFormat::kEacR11:
|
||||
case EtcFormat::kEacR11Signed:
|
||||
case EtcFormat::kEacRg11:
|
||||
case EtcFormat::kEacRg11Signed: {
|
||||
const bool is_signed = format == EtcFormat::kEacR11Signed ||
|
||||
format == EtcFormat::kEacRg11Signed;
|
||||
const int channels = texel_bytes / 2;
|
||||
for (int channel = 0; channel < channels; ++channel) {
|
||||
const std::uint16_t value =
|
||||
DecodeR11(block + channel * 8, texel, is_signed);
|
||||
out[channel * 2] = static_cast<std::uint8_t>(value);
|
||||
out[channel * 2 + 1] = static_cast<std::uint8_t>(value >> 8);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace mocktail::graphics
|
||||
@@ -0,0 +1,182 @@
|
||||
#include "mocktail/graphics/texture_override.h"
|
||||
|
||||
#include <png.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
|
||||
namespace mocktail::graphics {
|
||||
|
||||
std::uint64_t HashBytes(const std::uint8_t* data, std::size_t size) {
|
||||
std::uint64_t hash = 0xcbf29ce484222325ULL;
|
||||
for (std::size_t index = 0; index < size; ++index) {
|
||||
hash ^= data[index];
|
||||
hash *= 0x100000001b3ULL;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::string HashName(std::uint64_t hash) {
|
||||
char text[17];
|
||||
std::snprintf(text, sizeof(text), "%016llx",
|
||||
static_cast<unsigned long long>(hash));
|
||||
return text;
|
||||
}
|
||||
|
||||
bool ReadPngRgba(const std::string& path, RgbaImage* image) {
|
||||
if (image == nullptr) {
|
||||
return false;
|
||||
}
|
||||
png_image png{};
|
||||
png.version = PNG_IMAGE_VERSION;
|
||||
if (png_image_begin_read_from_file(&png, path.c_str()) == 0) {
|
||||
return false;
|
||||
}
|
||||
png.format = PNG_FORMAT_RGBA;
|
||||
RgbaImage result;
|
||||
result.width = png.width;
|
||||
result.height = png.height;
|
||||
result.pixels.resize(PNG_IMAGE_SIZE(png));
|
||||
if (png_image_finish_read(&png, nullptr, result.pixels.data(), 0,
|
||||
nullptr) == 0) {
|
||||
png_image_free(&png);
|
||||
return false;
|
||||
}
|
||||
*image = std::move(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WritePngRgba(const std::string& path, const RgbaImage& image) {
|
||||
if (image.width == 0 || image.height == 0 ||
|
||||
image.pixels.size() !=
|
||||
static_cast<std::size_t>(image.width) * image.height * 4) {
|
||||
return false;
|
||||
}
|
||||
png_image png{};
|
||||
png.version = PNG_IMAGE_VERSION;
|
||||
png.width = image.width;
|
||||
png.height = image.height;
|
||||
png.format = PNG_FORMAT_RGBA;
|
||||
const int written = png_image_write_to_file(&png, path.c_str(), 0,
|
||||
image.pixels.data(), 0, nullptr);
|
||||
png_image_free(&png);
|
||||
return written != 0;
|
||||
}
|
||||
|
||||
void ResampleRgba(const RgbaImage& source, std::uint32_t width,
|
||||
std::uint32_t height, std::uint8_t* destination) {
|
||||
if (source.pixels.size() <
|
||||
static_cast<std::size_t>(source.width) * source.height * 4) {
|
||||
return;
|
||||
}
|
||||
ResampleRgba(source.pixels.data(), source.width, source.height, width,
|
||||
height, destination);
|
||||
}
|
||||
|
||||
void ResampleRgba(const std::uint8_t* source, std::uint32_t source_width,
|
||||
std::uint32_t source_height, std::uint32_t width,
|
||||
std::uint32_t height, std::uint8_t* destination) {
|
||||
if (source == nullptr || destination == nullptr || width == 0 ||
|
||||
height == 0 || source_width == 0 || source_height == 0) {
|
||||
return;
|
||||
}
|
||||
for (std::uint32_t y = 0; y < height; ++y) {
|
||||
const std::uint64_t y0 = static_cast<std::uint64_t>(y) * source_height / height;
|
||||
std::uint64_t y1 =
|
||||
(static_cast<std::uint64_t>(y) + 1) * source_height / height;
|
||||
if (y1 <= y0) {
|
||||
y1 = y0 + 1;
|
||||
}
|
||||
for (std::uint32_t x = 0; x < width; ++x) {
|
||||
const std::uint64_t x0 = static_cast<std::uint64_t>(x) * source_width / width;
|
||||
std::uint64_t x1 =
|
||||
(static_cast<std::uint64_t>(x) + 1) * source_width / width;
|
||||
if (x1 <= x0) {
|
||||
x1 = x0 + 1;
|
||||
}
|
||||
std::uint64_t sum[4] = {0, 0, 0, 0};
|
||||
for (std::uint64_t sy = y0; sy < y1; ++sy) {
|
||||
for (std::uint64_t sx = x0; sx < x1; ++sx) {
|
||||
const std::uint8_t* pixel = source + (sy * source_width + sx) * 4;
|
||||
for (int channel = 0; channel < 4; ++channel) {
|
||||
sum[channel] += pixel[channel];
|
||||
}
|
||||
}
|
||||
}
|
||||
const std::uint64_t count = (y1 - y0) * (x1 - x0);
|
||||
std::uint8_t* out = destination + (static_cast<std::size_t>(y) * width + x) * 4;
|
||||
for (int channel = 0; channel < 4; ++channel) {
|
||||
out[channel] =
|
||||
static_cast<std::uint8_t>((sum[channel] + count / 2) / count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::string EnvironmentString(const char* name) {
|
||||
const char* value = std::getenv(name);
|
||||
return value != nullptr ? value : "";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TextureOverrides::TextureOverrides(std::string override_dir,
|
||||
std::string dump_dir)
|
||||
: override_dir_(std::move(override_dir)), dump_dir_(std::move(dump_dir)) {}
|
||||
|
||||
TextureOverrides TextureOverrides::FromEnvironment() {
|
||||
return TextureOverrides(EnvironmentString("MOCKTAIL_TEXTURE_OVERRIDE_DIR"),
|
||||
EnvironmentString("MOCKTAIL_TEXTURE_DUMP_DIR"));
|
||||
}
|
||||
|
||||
std::shared_ptr<const RgbaImage> TextureOverrides::Lookup(std::uint64_t hash) {
|
||||
if (override_dir_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
const auto cached = cache_.find(hash);
|
||||
if (cached != cache_.end()) {
|
||||
return cached->second;
|
||||
}
|
||||
std::shared_ptr<const RgbaImage> loaded;
|
||||
RgbaImage image;
|
||||
const std::string path = override_dir_ + "/" + HashName(hash) + ".png";
|
||||
if (ReadPngRgba(path, &image) && image.width > 0 && image.height > 0) {
|
||||
loaded = std::make_shared<const RgbaImage>(std::move(image));
|
||||
std::fprintf(stderr, " [vulkan] texture override loaded: %s (%ux%u)\n",
|
||||
path.c_str(), loaded->width, loaded->height);
|
||||
}
|
||||
cache_[hash] = loaded;
|
||||
return loaded;
|
||||
}
|
||||
|
||||
void TextureOverrides::Dump(std::uint64_t hash, std::uint32_t width,
|
||||
std::uint32_t height, const std::uint8_t* rgba) {
|
||||
if (dump_dir_.empty() || rgba == nullptr || width == 0 || height == 0) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (!dumped_.insert(hash).second) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::error_code error;
|
||||
std::filesystem::create_directories(dump_dir_, error);
|
||||
RgbaImage image;
|
||||
image.width = width;
|
||||
image.height = height;
|
||||
image.pixels.assign(rgba, rgba + static_cast<std::size_t>(width) * height * 4);
|
||||
const std::string path = dump_dir_ + "/" + HashName(hash) + "_" +
|
||||
std::to_string(width) + "x" +
|
||||
std::to_string(height) + ".png";
|
||||
if (!WritePngRgba(path, image)) {
|
||||
std::fprintf(stderr, " [vulkan] texture dump failed: %s\n", path.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mocktail::graphics
|
||||
File diff suppressed because it is too large.
Load diff
+50
-22
@@ -23,6 +23,8 @@
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "runtime/display_size.h"
|
||||
|
||||
namespace jnivm {
|
||||
|
||||
void Class::RegisterMethod(const std::string& method_name,
|
||||
@@ -562,13 +564,25 @@ bool ResolvedDarkTheme() {
|
||||
}
|
||||
|
||||
jint IntResultForName(const char* name) {
|
||||
if (std::strcmp(name, "getScreenWidth") == 0 ||
|
||||
std::strcmp(name, "getWidth") == 0) {
|
||||
return 1280;
|
||||
if (std::strcmp(name, "getScreenWidth") == 0) {
|
||||
return mocktail::runtime::ParseDisplaySize(
|
||||
std::getenv(mocktail::runtime::kDisplaySizeEnvironment))
|
||||
.width;
|
||||
}
|
||||
if (std::strcmp(name, "getScreenHeight") == 0 ||
|
||||
std::strcmp(name, "getHeight") == 0) {
|
||||
return 720;
|
||||
if (std::strcmp(name, "getScreenHeight") == 0) {
|
||||
return mocktail::runtime::ParseDisplaySize(
|
||||
std::getenv(mocktail::runtime::kDisplaySizeEnvironment))
|
||||
.height;
|
||||
}
|
||||
if (std::strcmp(name, "getWidth") == 0) {
|
||||
return mocktail::runtime::ParseDisplaySize(
|
||||
std::getenv(mocktail::runtime::kWindowSizeEnvironment))
|
||||
.width;
|
||||
}
|
||||
if (std::strcmp(name, "getHeight") == 0) {
|
||||
return mocktail::runtime::ParseDisplaySize(
|
||||
std::getenv(mocktail::runtime::kWindowSizeEnvironment))
|
||||
.height;
|
||||
}
|
||||
if (std::strcmp(name, "getDensityDpi") == 0) {
|
||||
return 160;
|
||||
@@ -2275,8 +2289,11 @@ jobject MakeDeviceStaticParamsObject() {
|
||||
pseudo_object->object_fields["soc_model"] =
|
||||
MakeString(identity.soc_model.c_str());
|
||||
pseudo_object->boolean_fields["cpu64Bit"] = JNI_TRUE;
|
||||
pseudo_object->int_fields["screenWidth"] = 1280;
|
||||
pseudo_object->int_fields["screenHeight"] = 720;
|
||||
const mocktail::runtime::DisplaySize host_display =
|
||||
mocktail::runtime::ParseDisplaySize(
|
||||
std::getenv(mocktail::runtime::kDisplaySizeEnvironment));
|
||||
pseudo_object->int_fields["screenWidth"] = host_display.width;
|
||||
pseudo_object->int_fields["screenHeight"] = host_display.height;
|
||||
pseudo_object->int_fields["screenDensityDpi"] = 160;
|
||||
pseudo_object->int_fields["apiVersion"] = 33;
|
||||
pseudo_object->int_fields["sdkVersion"] = 33;
|
||||
@@ -2446,14 +2463,17 @@ void EnsureAndroidObjectGraph() {
|
||||
SetObjectFieldRaw(decor_view, "holder", surface_holder);
|
||||
SetObjectFieldRaw(decor_view, "surface", surface);
|
||||
SetObjectFieldRaw(decor_view, "display", display);
|
||||
SetIntFieldRaw(decor_view, "width", 1280);
|
||||
SetIntFieldRaw(decor_view, "height", 720);
|
||||
const mocktail::runtime::DisplaySize host_window =
|
||||
mocktail::runtime::ParseDisplaySize(
|
||||
std::getenv(mocktail::runtime::kWindowSizeEnvironment));
|
||||
SetIntFieldRaw(decor_view, "width", host_window.width);
|
||||
SetIntFieldRaw(decor_view, "height", host_window.height);
|
||||
|
||||
SetObjectFieldRaw(surface_view, "holder", surface_holder);
|
||||
SetObjectFieldRaw(surface_view, "surface", surface);
|
||||
SetObjectFieldRaw(surface_view, "rootView", root_view);
|
||||
SetIntFieldRaw(surface_view, "width", 1280);
|
||||
SetIntFieldRaw(surface_view, "height", 720);
|
||||
SetIntFieldRaw(surface_view, "width", host_window.width);
|
||||
SetIntFieldRaw(surface_view, "height", host_window.height);
|
||||
|
||||
SetObjectFieldRaw(surface_holder, "surface", surface);
|
||||
SetObjectFieldRaw(surface_holder, "surfaceFrame",
|
||||
@@ -2461,11 +2481,14 @@ void EnsureAndroidObjectGraph() {
|
||||
SetBooleanFieldRaw(surface, "valid", JNI_TRUE);
|
||||
SetBooleanFieldRaw(surface, "isValid", JNI_TRUE);
|
||||
|
||||
SetIntFieldRaw(display, "width", 1280);
|
||||
SetIntFieldRaw(display, "height", 720);
|
||||
const mocktail::runtime::DisplaySize host_display =
|
||||
mocktail::runtime::ParseDisplaySize(
|
||||
std::getenv(mocktail::runtime::kDisplaySizeEnvironment));
|
||||
SetIntFieldRaw(display, "width", host_display.width);
|
||||
SetIntFieldRaw(display, "height", host_display.height);
|
||||
SetIntFieldRaw(display, "rotation", 0);
|
||||
SetIntFieldRaw(display_metrics, "widthPixels", 1280);
|
||||
SetIntFieldRaw(display_metrics, "heightPixels", 720);
|
||||
SetIntFieldRaw(display_metrics, "widthPixels", host_display.width);
|
||||
SetIntFieldRaw(display_metrics, "heightPixels", host_display.height);
|
||||
SetIntFieldRaw(display_metrics, "densityDpi", 160);
|
||||
SetFloatFieldRaw(display_metrics, "density", 1.0f);
|
||||
SetFloatFieldRaw(display_metrics, "scaledDensity", 1.0f);
|
||||
@@ -2476,9 +2499,10 @@ void EnsureAndroidObjectGraph() {
|
||||
: PlatformIdentity{};
|
||||
SetIntFieldRaw(configuration, "orientation", 2);
|
||||
SetIntFieldRaw(configuration, "densityDpi", 160);
|
||||
SetIntFieldRaw(configuration, "screenWidthDp", 1280);
|
||||
SetIntFieldRaw(configuration, "screenHeightDp", 720);
|
||||
SetIntFieldRaw(configuration, "smallestScreenWidthDp", 720);
|
||||
SetIntFieldRaw(configuration, "screenWidthDp", host_window.width);
|
||||
SetIntFieldRaw(configuration, "screenHeightDp", host_window.height);
|
||||
SetIntFieldRaw(configuration, "smallestScreenWidthDp",
|
||||
std::min(host_window.width, host_window.height));
|
||||
SetIntFieldRaw(configuration, "touchscreen", identity.touch_enabled ? 3 : 1);
|
||||
SetIntFieldRaw(configuration, "keyboard", identity.keyboard_enabled ? 2 : 1);
|
||||
SetIntFieldRaw(configuration, "keyboardHidden",
|
||||
@@ -4952,10 +4976,14 @@ jobject CreateAndroidConfiguration(JNIEnv* env) {
|
||||
SetIntFieldRaw(configuration, "navigation", 1);
|
||||
SetIntFieldRaw(configuration, "navigationHidden", 1);
|
||||
SetIntFieldRaw(configuration, "orientation", 2);
|
||||
SetIntFieldRaw(configuration, "screenHeightDp", 720);
|
||||
const mocktail::runtime::DisplaySize config_window =
|
||||
mocktail::runtime::ParseDisplaySize(
|
||||
std::getenv(mocktail::runtime::kWindowSizeEnvironment));
|
||||
SetIntFieldRaw(configuration, "screenHeightDp", config_window.height);
|
||||
SetIntFieldRaw(configuration, "screenLayout", 0);
|
||||
SetIntFieldRaw(configuration, "screenWidthDp", 1280);
|
||||
SetIntFieldRaw(configuration, "smallestScreenWidthDp", 720);
|
||||
SetIntFieldRaw(configuration, "screenWidthDp", config_window.width);
|
||||
SetIntFieldRaw(configuration, "smallestScreenWidthDp",
|
||||
std::min(config_window.width, config_window.height));
|
||||
SetIntFieldRaw(configuration, "touchscreen", identity.touch_enabled ? 3 : 1);
|
||||
SetIntFieldRaw(configuration, "uiMode", 0);
|
||||
return configuration;
|
||||
|
||||
+104
-27
@@ -3,6 +3,7 @@
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@@ -56,6 +57,8 @@
|
||||
#include "libc_shim/libc_shim.h"
|
||||
#include "linker/linker.h"
|
||||
#include "mocktail/graphics/bionic_egl_bridge.h"
|
||||
#include "runtime/device_memory_profile.h"
|
||||
#include "runtime/display_size.h"
|
||||
#include "runtime/environment.h"
|
||||
#include "runtime/discord_rpc.h"
|
||||
#include "runtime/jnivm_platform_web_callbacks.h"
|
||||
@@ -70,6 +73,7 @@
|
||||
#include "runtime/roblox_text_input_jni_bridge.h"
|
||||
#include "runtime/runtime_config.h"
|
||||
#include "runtime/runtime_paths.h"
|
||||
#include "runtime/texture_memory_policy.h"
|
||||
#include "services/client_settings_service.h"
|
||||
#include "services/http_client.h"
|
||||
#include "window/window.h"
|
||||
@@ -2393,6 +2397,21 @@ void InstallNativeGlJavaImplementation(JNIEnv* env, jclass native_gl_java_class)
|
||||
|
||||
jobject BuildPlatformParams(JNIEnv* env, jobject surface, bool is_headless);
|
||||
|
||||
mocktail::runtime::DisplaySize HostDisplaySize() {
|
||||
return mocktail::runtime::ParseDisplaySize(
|
||||
std::getenv(mocktail::runtime::kDisplaySizeEnvironment));
|
||||
}
|
||||
|
||||
mocktail::runtime::DisplaySize HostWindowPixelSize() {
|
||||
const mocktail::window::WindowViewportSnapshot viewport =
|
||||
mocktail::window::GetWindowViewportSnapshot();
|
||||
if (viewport.valid()) {
|
||||
return {viewport.pixel_width, viewport.pixel_height};
|
||||
}
|
||||
return mocktail::runtime::ParseDisplaySize(
|
||||
std::getenv(mocktail::runtime::kWindowSizeEnvironment));
|
||||
}
|
||||
|
||||
jobject BuildDeviceParams(JNIEnv* env) {
|
||||
jobject params = NewObject(env, "com/roblox/engine/jni/model/DeviceParams");
|
||||
if (!params) {
|
||||
@@ -2414,27 +2433,40 @@ jobject BuildDeviceParams(JNIEnv* env) {
|
||||
SetStringField(env, params, "appVersion", app_version.c_str());
|
||||
SetStringField(env, params, "country", "US");
|
||||
SetStringField(env, params, "manufacturer", manufacturer.c_str());
|
||||
SetStringField(env, params, "displayResolution", "1280x720");
|
||||
const mocktail::runtime::DisplaySize display_size =
|
||||
mocktail::runtime::ParseDisplaySize(
|
||||
std::getenv(mocktail::runtime::kDisplaySizeEnvironment));
|
||||
const std::string display_resolution = std::to_string(display_size.width) +
|
||||
"x" +
|
||||
std::to_string(display_size.height);
|
||||
SetStringField(env, params, "displayResolution", display_resolution.c_str());
|
||||
SetStringField(env, params, "networkType", "wifi");
|
||||
SetStringField(env, params, "deviceSku", device_sku.c_str());
|
||||
SetStringField(env, params, "socModel", soc_model.c_str());
|
||||
SetStringField(env, params, "appBuildVariant", "googleProdRelease");
|
||||
SetStringField(env, params, "testDeviceName", device_name.c_str());
|
||||
|
||||
const bool is_low_ram = !IsEnabled("MOCKTAIL_DISABLE_LOW_RAM_DEVICE");
|
||||
SetIntField(env, params, "deviceTotalMemoryMB", is_low_ram ? 2048 : 4096);
|
||||
SetIntField(env, params, "displayPhysicalWidthPixels", 1280);
|
||||
SetIntField(env, params, "displayPhysicalHeightPixels", 720);
|
||||
SetIntField(env, params, "memoryClass", is_low_ram ? 256 : 512);
|
||||
SetIntField(env, params, "largeMemoryClass", is_low_ram ? 512 : 1024);
|
||||
std::uint64_t host_memory_bytes = mocktail::runtime::DetectHostMemoryBytes();
|
||||
if (IsEnabled("MOCKTAIL_DISABLE_LOW_RAM_DEVICE")) {
|
||||
host_memory_bytes =
|
||||
std::max<std::uint64_t>(host_memory_bytes, 4096ULL * 1024ULL * 1024ULL);
|
||||
}
|
||||
const mocktail::runtime::DeviceMemoryProfile memory =
|
||||
mocktail::runtime::BuildDeviceMemoryProfile(host_memory_bytes);
|
||||
SetIntField(env, params, "deviceTotalMemoryMB", memory.total_memory_mb);
|
||||
SetIntField(env, params, "displayPhysicalWidthPixels", display_size.width);
|
||||
SetIntField(env, params, "displayPhysicalHeightPixels",
|
||||
display_size.height);
|
||||
SetIntField(env, params, "memoryClass", memory.memory_class_mb);
|
||||
SetIntField(env, params, "largeMemoryClass", memory.large_memory_class_mb);
|
||||
SetLongField(env, params, "lowMemoryKillerBackgroundAppThreshold",
|
||||
is_low_ram ? 256 : 0);
|
||||
memory.low_memory_killer_background_threshold);
|
||||
SetLongField(env, params, "lowMemoryKillerForegroundAppThreshold",
|
||||
is_low_ram ? 512 : 0);
|
||||
memory.low_memory_killer_foreground_threshold);
|
||||
SetBooleanField(env, params, "cpu64Bit", JNI_TRUE);
|
||||
SetBooleanField(env, params, "isChrome", JNI_FALSE);
|
||||
SetBooleanField(env, params, "isLowRamDevice",
|
||||
is_low_ram ? JNI_TRUE : JNI_FALSE);
|
||||
memory.low_ram_device ? JNI_TRUE : JNI_FALSE);
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -2534,8 +2566,9 @@ jobject BuildAppBridgeInitParams(JNIEnv* env, jstring client_settings,
|
||||
SetStringField(env, params, "locale", "en_us");
|
||||
SetRobloxServiceUrlFields(env, params);
|
||||
|
||||
SetIntField(env, params, "screenWidth", 1280);
|
||||
SetIntField(env, params, "screenHeight", 720);
|
||||
const mocktail::runtime::DisplaySize init_display = HostDisplaySize();
|
||||
SetIntField(env, params, "screenWidth", init_display.width);
|
||||
SetIntField(env, params, "screenHeight", init_display.height);
|
||||
SetIntField(env, params, "densityDpi", 160);
|
||||
SetIntField(env, params, "sdkVersion", 33);
|
||||
jboolean headless = is_headless ? JNI_TRUE : JNI_FALSE;
|
||||
@@ -2569,13 +2602,19 @@ jobject BuildPlatformParams(JNIEnv* env, jobject surface, bool is_headless) {
|
||||
SetStringField(env, params, "deviceName", platform_device_name.c_str());
|
||||
SetStringField(env, params, "locale", "en_us");
|
||||
SetStringField(env, params, "assetFolderPath", DefaultAssetPath().c_str());
|
||||
SetIntField(env, params, "width", 1280);
|
||||
SetIntField(env, params, "height", 720);
|
||||
SetIntField(env, params, "screenWidth", 1280);
|
||||
SetIntField(env, params, "screenHeight", 720);
|
||||
const mocktail::runtime::DisplaySize platform_window = HostWindowPixelSize();
|
||||
const mocktail::runtime::DisplaySize platform_display = HostDisplaySize();
|
||||
SetIntField(env, params, "width", platform_window.width);
|
||||
SetIntField(env, params, "height", platform_window.height);
|
||||
SetIntField(env, params, "screenWidth", platform_display.width);
|
||||
SetIntField(env, params, "screenHeight", platform_display.height);
|
||||
SetIntField(env, params, "densityDpi", 160);
|
||||
SetIntField(env, params, "viewportWidthMm", 203);
|
||||
SetIntField(env, params, "viewportHeightMm", 114);
|
||||
SetIntField(env, params, "viewportWidthMm",
|
||||
mocktail::runtime::PixelsToMillimetersAt160Dpi(
|
||||
platform_window.width));
|
||||
SetIntField(env, params, "viewportHeightMm",
|
||||
mocktail::runtime::PixelsToMillimetersAt160Dpi(
|
||||
platform_window.height));
|
||||
const mocktail::window::WindowViewportSnapshot viewport =
|
||||
mocktail::window::GetWindowViewportSnapshot();
|
||||
SetFloatField(env, params, "dpiScale", viewport.dpi_scale);
|
||||
@@ -2684,10 +2723,12 @@ jobject BuildStartAppParams(JNIEnv* env, jstring app_params,
|
||||
SetStringField(env, params, "deviceName", start_device_name.c_str());
|
||||
SetStringField(env, params, "locale", "en_us");
|
||||
SetRobloxServiceUrlFields(env, params);
|
||||
SetIntField(env, params, "width", 1280);
|
||||
SetIntField(env, params, "height", 720);
|
||||
SetIntField(env, params, "screenWidth", 1280);
|
||||
SetIntField(env, params, "screenHeight", 720);
|
||||
const mocktail::runtime::DisplaySize start_window = HostWindowPixelSize();
|
||||
const mocktail::runtime::DisplaySize start_display = HostDisplaySize();
|
||||
SetIntField(env, params, "width", start_window.width);
|
||||
SetIntField(env, params, "height", start_window.height);
|
||||
SetIntField(env, params, "screenWidth", start_display.width);
|
||||
SetIntField(env, params, "screenHeight", start_display.height);
|
||||
SetIntField(env, params, "densityDpi", 160);
|
||||
SetIntField(env, params, "sdkVersion", 33);
|
||||
SetIntField(env, params, "placeId", 0);
|
||||
@@ -2815,8 +2856,9 @@ jobject BuildMockSurface(JNIEnv* env) {
|
||||
if (!surface) {
|
||||
return nullptr;
|
||||
}
|
||||
SetIntField(env, surface, "width", 1280);
|
||||
SetIntField(env, surface, "height", 720);
|
||||
const mocktail::runtime::DisplaySize surface_size = HostWindowPixelSize();
|
||||
SetIntField(env, surface, "width", surface_size.width);
|
||||
SetIntField(env, surface, "height", surface_size.height);
|
||||
SetBooleanField(env, surface, "valid", JNI_TRUE);
|
||||
SetBooleanField(env, surface, "isValid", JNI_TRUE);
|
||||
return surface;
|
||||
@@ -3913,7 +3955,10 @@ std::cerr << " [engine] nativeAppBridgeV2InitWithParams recovered\n"
|
||||
on_surface_created(env, game_activity, handle, surface);
|
||||
}
|
||||
if (run_lifecycle_callbacks && on_surface_changed) {
|
||||
on_surface_changed(env, game_activity, handle, surface, 4, 1280, 720);
|
||||
const mocktail::runtime::DisplaySize changed_size =
|
||||
HostWindowPixelSize();
|
||||
on_surface_changed(env, game_activity, handle, surface, 4,
|
||||
changed_size.width, changed_size.height);
|
||||
}
|
||||
if (run_lifecycle_callbacks && on_surface_redraw_needed) {
|
||||
on_surface_redraw_needed(env, game_activity, handle, surface);
|
||||
@@ -4084,8 +4129,9 @@ std::cerr << " [engine] nativeAppBridgeV2InitWithParams recovered\n"
|
||||
ShouldRunStartupStep("MOCKTAIL_UPDATE_APP_UI_SIZES", false)) {
|
||||
env = ensure_env();
|
||||
std::cout << " [engine] updateAppUISizes\n" << std::flush;
|
||||
context->native_update_app_ui_sizes(env, native_gl_class, 1280, 720, 0, 0,
|
||||
0);
|
||||
const mocktail::runtime::DisplaySize ui_size = HostWindowPixelSize();
|
||||
context->native_update_app_ui_sizes(env, native_gl_class, ui_size.width,
|
||||
ui_size.height, 0, 0, 0);
|
||||
std::cout << " [engine] updateAppUISizes returned\n" << std::flush;
|
||||
}
|
||||
|
||||
@@ -4726,6 +4772,37 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options,
|
||||
<< std::flush;
|
||||
}
|
||||
}
|
||||
if (window_initialised) {
|
||||
auto* sdl_window =
|
||||
static_cast<SDL_Window*>(mocktail::window::GetBackendWindow());
|
||||
const SDL_DisplayID display =
|
||||
sdl_window != nullptr ? SDL_GetDisplayForWindow(sdl_window) : 0;
|
||||
const SDL_DisplayMode* mode =
|
||||
display != 0 ? SDL_GetCurrentDisplayMode(display) : nullptr;
|
||||
if (mode != nullptr && mode->w > 0 && mode->h > 0) {
|
||||
// SDL mode dimensions need pixel_density to become physical pixels.
|
||||
const float density = mode->pixel_density > 0.0f ? mode->pixel_density
|
||||
: 1.0f;
|
||||
const std::string display_size =
|
||||
std::to_string(std::lround(mode->w * density)) + "x" +
|
||||
std::to_string(std::lround(mode->h * density));
|
||||
setenv(mocktail::runtime::kDisplaySizeEnvironment, display_size.c_str(),
|
||||
1);
|
||||
std::cout << " [window] host display " << display_size << " pixels\n"
|
||||
<< std::flush;
|
||||
}
|
||||
}
|
||||
if (window_initialised) {
|
||||
const mocktail::window::WindowViewportSnapshot viewport =
|
||||
mocktail::window::GetWindowViewportSnapshot();
|
||||
if (viewport.valid()) {
|
||||
const std::string window_size = std::to_string(viewport.pixel_width) +
|
||||
"x" +
|
||||
std::to_string(viewport.pixel_height);
|
||||
setenv(mocktail::runtime::kWindowSizeEnvironment, window_size.c_str(),
|
||||
1);
|
||||
}
|
||||
}
|
||||
const SDL_SystemTheme system_theme =
|
||||
window_initialised ? SDL_GetSystemTheme() : SDL_SYSTEM_THEME_UNKNOWN;
|
||||
const bool system_dark_theme = system_theme == SDL_SYSTEM_THEME_DARK;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "runtime/device_memory_profile.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
namespace mocktail {
|
||||
namespace runtime {
|
||||
namespace {
|
||||
|
||||
constexpr std::uint64_t kMebibyte = 1024U * 1024U;
|
||||
constexpr std::uint64_t kMinimumNormalRamBytes = 4096U * kMebibyte;
|
||||
|
||||
} // namespace
|
||||
|
||||
DeviceMemoryProfile BuildDeviceMemoryProfile(std::uint64_t host_memory_bytes) {
|
||||
DeviceMemoryProfile profile;
|
||||
if (host_memory_bytes < kMinimumNormalRamBytes) {
|
||||
profile.total_memory_mb = 2048;
|
||||
profile.memory_class_mb = 256;
|
||||
profile.large_memory_class_mb = 512;
|
||||
profile.low_memory_killer_background_threshold = 256;
|
||||
profile.low_memory_killer_foreground_threshold = 512;
|
||||
profile.low_ram_device = true;
|
||||
return profile;
|
||||
}
|
||||
profile.total_memory_mb = static_cast<std::int32_t>(std::min<std::uint64_t>(
|
||||
host_memory_bytes / kMebibyte,
|
||||
static_cast<std::uint64_t>(std::numeric_limits<std::int32_t>::max())));
|
||||
profile.memory_class_mb = 512;
|
||||
profile.large_memory_class_mb = 1024;
|
||||
return profile;
|
||||
}
|
||||
|
||||
} // namespace runtime
|
||||
} // namespace mocktail
|
||||
@@ -18,11 +18,8 @@ namespace mocktail {
|
||||
namespace runtime {
|
||||
namespace {
|
||||
|
||||
// TextureManager2 selects its mobile MipPackStream path on Android Vulkan and
|
||||
// can keep world textures at low-resolution mips. TM1 preserves full texture
|
||||
// fidelity until that path is compatible with the host Vulkan adapter.
|
||||
constexpr char kVulkanClientSettingsOverrides[] =
|
||||
R"({"FStringGraphicsTextureManager2DenyPattern2":".*","FStringGraphicsVulkanShaderMTDenyPattern":"4318:.*"})";
|
||||
R"({"FStringGraphicsVulkanShaderMTDenyPattern":"4318:.*"})";
|
||||
|
||||
constexpr const char* kIcdDirectories[] = {
|
||||
"/usr/share/vulkan/icd.d",
|
||||
|
||||
@@ -244,7 +244,7 @@ struct TextBytePosition {
|
||||
};
|
||||
|
||||
TextBytePosition TextPosition(TTF_Text* layout, std::size_t byte_offset,
|
||||
int text_width) {
|
||||
int text_width, bool single_line) {
|
||||
if (layout == nullptr) {
|
||||
return {};
|
||||
}
|
||||
@@ -259,7 +259,11 @@ TextBytePosition TextPosition(TTF_Text* layout, std::size_t byte_offset,
|
||||
position.y = substring.rect.y;
|
||||
position.height = substring.rect.h;
|
||||
if ((substring.flags & TTF_SUBSTRING_TEXT_END) != 0) {
|
||||
position.x = std::min(text_width, substring.rect.x + substring.rect.w);
|
||||
// Trailing whitespace has no ink, so the final cluster rect stops short of
|
||||
// the laid-out width that a caret at the end of a single line sits behind.
|
||||
position.x = single_line ? text_width
|
||||
: std::min(text_width,
|
||||
substring.rect.x + substring.rect.w);
|
||||
}
|
||||
return position;
|
||||
}
|
||||
@@ -501,11 +505,12 @@ Status RobloxTextSurfaceOverlay::RasterizeLocked() {
|
||||
TextBytePosition selection_end_position;
|
||||
if (layout != nullptr) {
|
||||
(void)TTF_GetTextSize(layout, &text_width, &text_height);
|
||||
caret_position = TextPosition(layout, caret_byte, text_width);
|
||||
selection_begin_position =
|
||||
TextPosition(layout, selection_begin_byte, text_width);
|
||||
caret_position =
|
||||
TextPosition(layout, caret_byte, text_width, !wrapped_layout);
|
||||
selection_begin_position = TextPosition(layout, selection_begin_byte,
|
||||
text_width, !wrapped_layout);
|
||||
selection_end_position =
|
||||
TextPosition(layout, selection_end_byte, text_width);
|
||||
TextPosition(layout, selection_end_byte, text_width, !wrapped_layout);
|
||||
}
|
||||
if (!text.value.empty()) {
|
||||
// SDL_ttf implementations have historically differed on whether fg.a is
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Includes changes by vii from CoderDayton/nightcap.
|
||||
#include "runtime/runtime_paths.h"
|
||||
|
||||
#define JSON_NOEXCEPTION 1
|
||||
@@ -603,7 +604,9 @@ bool ExportRuntimePathEnvironment(const RuntimePaths& paths,
|
||||
SetEnvironmentDefault("MOCKTAIL_ANDROID_CACHE_HOST_ROOT",
|
||||
paths.android_cache_root(), error) &&
|
||||
SetEnvironmentDefault("MOCKTAIL_VULKAN_SHADER_CACHE_HOST_PATH",
|
||||
paths.vulkan_shader_cache_file(), error);
|
||||
paths.vulkan_shader_cache_file(), error) &&
|
||||
SetEnvironmentDefault("MOCKTAIL_TEXTURE_OVERRIDE_DIR",
|
||||
paths.config_root() / "textures", error);
|
||||
}
|
||||
|
||||
bool PrepareManagedPayloadWorkingDirectory(const RuntimePaths& paths,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Includes changes by vii from CoderDayton/nightcap.
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
# Licensed under the Apache License, Version 2.0.
|
||||
|
||||
@@ -106,13 +107,19 @@ target_link_libraries(stub_glesv2 PRIVATE dl)
|
||||
add_library(stub_vulkan SHARED
|
||||
${CMAKE_SOURCE_DIR}/src/graphics/bionic_vulkan_loader_adapter.cc
|
||||
${CMAKE_SOURCE_DIR}/src/graphics/vulkan_text_overlay_compositor.cc
|
||||
${CMAKE_SOURCE_DIR}/src/graphics/vulkan_etc2_emulation.cc
|
||||
${CMAKE_SOURCE_DIR}/src/graphics/etc2_decoder.cc
|
||||
${CMAKE_SOURCE_DIR}/src/graphics/texture_override.cc
|
||||
)
|
||||
configure_stub(stub_vulkan "libvulkan.so")
|
||||
target_include_directories(stub_vulkan PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
pkg_check_modules(LIBPLACEBO REQUIRED IMPORTED_TARGET libplacebo)
|
||||
pkg_check_modules(LIBPNG REQUIRED IMPORTED_TARGET libpng)
|
||||
target_link_libraries(stub_vulkan PRIVATE
|
||||
Mocktail::SdlVulkanWsi
|
||||
PkgConfig::LIBPLACEBO
|
||||
PkgConfig::LIBPNG
|
||||
Threads::Threads
|
||||
dl
|
||||
)
|
||||
|
||||
|
||||
@@ -569,6 +569,31 @@ TEST(RobloxTextDisplayStateTest, WrapsMultilineTextInsideNativeTextBox) {
|
||||
EXPECT_TRUE(overlay.Shutdown().ok());
|
||||
}
|
||||
|
||||
TEST(RobloxTextDisplayStateTest, AdvancesCaretForTrailingSpace) {
|
||||
RobloxTextSurfaceOverlay overlay;
|
||||
ASSERT_TRUE(overlay.Initialize({800, 600}).ok());
|
||||
RobloxTextDisplaySink sink = overlay.sink();
|
||||
const auto rightmost_pixel = [&](const std::string& text,
|
||||
RobloxTextDisplayEvent event) {
|
||||
RobloxTextDisplayUpdate update =
|
||||
Show(1, text, static_cast<int32_t>(text.size()));
|
||||
update.event = event;
|
||||
update.font_size = 18.0F;
|
||||
sink.update(sink.context, update);
|
||||
MocktailTextOverlayFrameInfo frame;
|
||||
EXPECT_TRUE(overlay.QueryFrame(&frame));
|
||||
std::vector<std::uint8_t> rgba(frame.rgba_bytes);
|
||||
EXPECT_TRUE(overlay.CopyFrame(frame.revision, rgba.data(), rgba.size()));
|
||||
return FindAlphaBounds(frame, rgba).maximum_x;
|
||||
};
|
||||
|
||||
const int typed = rightmost_pixel("hi", RobloxTextDisplayEvent::kShow);
|
||||
const int spaced = rightmost_pixel("hi ", RobloxTextDisplayEvent::kUpdate);
|
||||
|
||||
EXPECT_GT(spaced, typed);
|
||||
EXPECT_TRUE(overlay.Shutdown().ok());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace runtime
|
||||
} // namespace mocktail
|
||||
@@ -182,7 +182,7 @@ int RunGraphicsPolicyProbe(const char* backend) {
|
||||
std::string(anv_memory_limit) == expected_anv_limit &&
|
||||
std::string(submit_thread) == "1" &&
|
||||
std::string(overrides).find(
|
||||
"\"FStringGraphicsTextureManager2DenyPattern2\":\".*\"") !=
|
||||
"FStringGraphicsTextureManager2DenyPattern2") ==
|
||||
std::string::npos &&
|
||||
std::string(overrides).find(
|
||||
"FStringGraphicsVulkanShaderMTDenyPattern") !=
|
||||
|
||||
Reference in new issue
Block a user