Merge branch 'master' into vulkan

This commit is contained in:
Triang3l
2021-10-31 16:27:37 +03:00
40 changed files with 897 additions and 239 deletions

View File

@@ -429,7 +429,7 @@ void EmulatorWindow::ShowCommitID() {
"https://github.com/xenia-project/xenia/pull/" XE_BUILD_PR_NUMBER);
#else
LaunchWebBrowser(
"https://github.com/xenia-project/xenia/commit/" XE_BUILD_COMMIT "/");
"https://github.com/xenia-project/xenia/commit/" XE_BUILD_COMMIT);
#endif
}

View File

@@ -48,7 +48,7 @@ void Arena::DebugFill() {
void* Arena::Alloc(size_t size, size_t align) {
assert_true(
xe::bit_count(align) == 1 && align <= 16,
align > 0 && xe::is_pow2(align) && align <= 16,
"align needs to be a power of 2 and not greater than Chunk alignment");
// for alignment

View File

@@ -16,46 +16,7 @@
namespace xe {
// These functions are modeled off of the Apple OSAtomic routines
// https://developer.apple.com/documentation/kernel/osatomic_h (?)
// Original link (dead):
// https://developer.apple.com/library/mac/#documentation/DriversKernelHardware/Reference/libkern_ref/OSAtomic_h/
#if XE_PLATFORM_MAC
inline int32_t atomic_inc(volatile int32_t* value) {
return OSAtomicIncrement32Barrier(reinterpret_cast<volatile int32_t*>(value));
}
inline int32_t atomic_dec(volatile int32_t* value) {
return OSAtomicDecrement32Barrier(reinterpret_cast<volatile int32_t*>(value));
}
inline int32_t atomic_exchange(int32_t new_value, volatile int32_t* value) {
return OSAtomicCompareAndSwap32Barrier(*value, new_value, value);
}
inline int64_t atomic_exchange(int64_t new_value, volatile int64_t* value) {
return OSAtomicCompareAndSwap64Barrier(*value, new_value, value);
}
inline int32_t atomic_exchange_add(int32_t amount, volatile int32_t* value) {
return OSAtomicAdd32Barrier(amount, value) - amount;
}
inline int64_t atomic_exchange_add(int64_t amount, volatile int64_t* value) {
return OSAtomicAdd64Barrier(amount, value) - amount;
}
inline bool atomic_cas(int32_t old_value, int32_t new_value,
volatile int32_t* value) {
return OSAtomicCompareAndSwap32Barrier(
old_value, new_value, reinterpret_cast<volatile int32_t*>(value));
}
inline bool atomic_cas(int64_t old_value, int64_t new_value,
volatile int64_t* value) {
return OSAtomicCompareAndSwap64Barrier(
old_value, new_value, reinterpret_cast<volatile int64_t*>(value));
}
#elif XE_PLATFORM_WIN32
#if XE_PLATFORM_WIN32
inline int32_t atomic_inc(volatile int32_t* value) {
return _InterlockedIncrement(reinterpret_cast<volatile long*>(value));
@@ -94,7 +55,7 @@ inline bool atomic_cas(int64_t old_value, int64_t new_value,
old_value) == old_value;
}
#elif XE_PLATFORM_LINUX
#elif XE_PLATFORM_LINUX || XE_PLATFORM_MAC
inline int32_t atomic_inc(volatile int32_t* value) {
return __sync_add_and_fetch(value, 1);
@@ -132,7 +93,7 @@ inline bool atomic_cas(int64_t old_value, int64_t new_value,
#error No atomic primitives defined for this platform/cpu combination.
#endif // OSX
#endif // XE_PLATFORM
inline uint32_t atomic_inc(volatile uint32_t* value) {
return static_cast<uint32_t>(

View File

@@ -80,7 +80,7 @@ uint64_t Clock::host_tick_frequency_raw() {
// For some CPUs, Crystal frequency is not reported.
if (ratio_num && ratio_den && cryst_freq) {
// If it is, calculate the TSC frequency
auto tsc_freq = cryst_freq * ratio_num / ratio_den;
return cryst_freq * ratio_num / ratio_den;
}
}

View File

@@ -280,8 +280,7 @@ class Logger {
// many blocks needed for at least one log line.
auto next_range = dp::sequence_range(next_sequence, desired_count);
auto available_sequence = claim_strategy_.wait_until_published(
next_range.last(), last_sequence);
claim_strategy_.wait_until_published(next_range.last(), last_sequence);
size_t read_count = 0;
auto available_range = next_range;

View File

@@ -48,10 +48,10 @@ void copy_128_aligned(void* dest, const void* src, size_t count) {
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100801
// TODO(Joel Linn): Remove this when fixed GCC versions are common place.
#if XE_COMPILER_GNUC
#define XE_WORKAROUND_LOOP_KILL_MOD(x) \
if ((count % (x)) == 0) __builtin_unreachable();
#define XE_WORKAROUND_CONSTANT_RETURN_IF(x) \
if (__builtin_constant_p(x) && (x)) return;
#else
#define XE_WORKAROUND_LOOP_KILL_MOD(x)
#define XE_WORKAROUND_CONSTANT_RETURN_IF(x)
#endif
void copy_and_swap_16_aligned(void* dest_ptr, const void* src_ptr,
size_t count) {
@@ -70,8 +70,8 @@ void copy_and_swap_16_aligned(void* dest_ptr, const void* src_ptr,
__m128i output = _mm_shuffle_epi8(input, shufmask);
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
}
XE_WORKAROUND_CONSTANT_RETURN_IF(count % 8 == 0);
for (; i < count; ++i) { // handle residual elements
XE_WORKAROUND_LOOP_KILL_MOD(8);
dest[i] = byte_swap(src[i]);
}
}
@@ -90,8 +90,8 @@ void copy_and_swap_16_unaligned(void* dest_ptr, const void* src_ptr,
__m128i output = _mm_shuffle_epi8(input, shufmask);
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
}
XE_WORKAROUND_CONSTANT_RETURN_IF(count % 8 == 0);
for (; i < count; ++i) { // handle residual elements
XE_WORKAROUND_LOOP_KILL_MOD(8);
dest[i] = byte_swap(src[i]);
}
}
@@ -113,8 +113,8 @@ void copy_and_swap_32_aligned(void* dest_ptr, const void* src_ptr,
__m128i output = _mm_shuffle_epi8(input, shufmask);
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
}
XE_WORKAROUND_CONSTANT_RETURN_IF(count % 4 == 0);
for (; i < count; ++i) { // handle residual elements
XE_WORKAROUND_LOOP_KILL_MOD(4);
dest[i] = byte_swap(src[i]);
}
}
@@ -133,8 +133,8 @@ void copy_and_swap_32_unaligned(void* dest_ptr, const void* src_ptr,
__m128i output = _mm_shuffle_epi8(input, shufmask);
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
}
XE_WORKAROUND_CONSTANT_RETURN_IF(count % 4 == 0);
for (; i < count; ++i) { // handle residual elements
XE_WORKAROUND_LOOP_KILL_MOD(4);
dest[i] = byte_swap(src[i]);
}
}
@@ -156,8 +156,8 @@ void copy_and_swap_64_aligned(void* dest_ptr, const void* src_ptr,
__m128i output = _mm_shuffle_epi8(input, shufmask);
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
}
XE_WORKAROUND_CONSTANT_RETURN_IF(count % 2 == 0);
for (; i < count; ++i) { // handle residual elements
XE_WORKAROUND_LOOP_KILL_MOD(2);
dest[i] = byte_swap(src[i]);
}
}
@@ -176,8 +176,8 @@ void copy_and_swap_64_unaligned(void* dest_ptr, const void* src_ptr,
__m128i output = _mm_shuffle_epi8(input, shufmask);
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
}
XE_WORKAROUND_CONSTANT_RETURN_IF(count % 2 == 0);
for (; i < count; ++i) { // handle residual elements
XE_WORKAROUND_LOOP_KILL_MOD(2);
dest[i] = byte_swap(src[i]);
}
}
@@ -193,8 +193,8 @@ void copy_and_swap_16_in_32_aligned(void* dest_ptr, const void* src_ptr,
_mm_or_si128(_mm_slli_epi32(input, 16), _mm_srli_epi32(input, 16));
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
}
XE_WORKAROUND_CONSTANT_RETURN_IF(count % 4 == 0);
for (; i < count; ++i) { // handle residual elements
XE_WORKAROUND_LOOP_KILL_MOD(4);
dest[i] = (src[i] >> 16) | (src[i] << 16);
}
}
@@ -210,8 +210,8 @@ void copy_and_swap_16_in_32_unaligned(void* dest_ptr, const void* src_ptr,
_mm_or_si128(_mm_slli_epi32(input, 16), _mm_srli_epi32(input, 16));
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
}
XE_WORKAROUND_CONSTANT_RETURN_IF(count % 4 == 0);
for (; i < count; ++i) { // handle residual elements
XE_WORKAROUND_LOOP_KILL_MOD(4);
dest[i] = (src[i] >> 16) | (src[i] << 16);
}
}

View File

@@ -241,7 +241,6 @@ std::string_view::size_type find_any_of(const std::string_view haystack,
auto [haystack_begin, haystack_end] = make_citer(haystack);
auto [needle_begin, needle_end] = make_citer(needles);
auto needle_count = count(needles);
auto it = find_needle(haystack_begin, haystack_end, needle_begin, needle_end);
if (it == haystack_end) {
@@ -261,7 +260,6 @@ std::string_view::size_type find_any_of_case(const std::string_view haystack,
auto [haystack_begin, haystack_end] = make_citer(haystack);
auto [needle_begin, needle_end] = make_citer(needles);
auto needle_count = count(needles);
auto it =
find_needle_case(haystack_begin, haystack_end, needle_begin, needle_end);

View File

@@ -876,8 +876,9 @@ bool GetResolveInfo(const RegisterFile& regs, const Memory& memory,
}
info_out.address.copy_sample_select = sample_select;
// Get the format to pass to the shader in a unified way - for depth (for
// which Direct3D 9 specifies the k_8_8_8_8 destination format), make sure the
// shader won't try to do conversion - pass proper k_24_8 or k_24_8_FLOAT.
// which Direct3D 9 specifies the k_8_8_8_8 uint destination format), make
// sure the shader won't try to do conversion - pass proper k_24_8 or
// k_24_8_FLOAT.
auto rb_copy_dest_info = regs.Get<reg::RB_COPY_DEST_INFO>();
xenos::TextureFormat dest_format;
auto rb_depth_info = regs.Get<reg::RB_DEPTH_INFO>();

View File

@@ -15,6 +15,14 @@ namespace xe {
namespace gpu {
using namespace ucode;
// TODO(Triang3l): Support sub-dword memexports (like k_8 in 58410B86). This
// would require four 128 MB R8_UINT UAVs due to the Nvidia addressing limit.
// Need to be careful with resource binding tiers, however. Resource binding
// tier 1 on feature level 11_0 allows only 8 UAVs _across all stages_.
// RWByteAddressBuffer + 4 typed buffers is 5 per stage already, would need 10
// for both VS and PS, or even 11 with the eDRAM ROV. Need to drop draw commands
// doing memexport in both VS and PS on FL 11_0 resource binding tier 1.
void DxbcShaderTranslator::ExportToMemory_PackFixed32(
const uint32_t* eM_temps, uint32_t eM_count, const uint32_t bits[4],
const dxbc::Src& is_integer, const dxbc::Src& is_signed) {

View File

@@ -712,14 +712,14 @@ static_assert_size(RB_COPY_CONTROL, sizeof(uint32_t));
union alignas(uint32_t) RB_COPY_DEST_INFO {
struct {
xenos::Endian128 copy_dest_endian : 3; // +0
uint32_t copy_dest_array : 1; // +3
uint32_t copy_dest_slice : 3; // +4
xenos::ColorFormat copy_dest_format : 6; // +7
uint32_t copy_dest_number : 3; // +13
int32_t copy_dest_exp_bias : 6; // +16
uint32_t : 2; // +22
uint32_t copy_dest_swap : 1; // +24
xenos::Endian128 copy_dest_endian : 3; // +0
uint32_t copy_dest_array : 1; // +3
uint32_t copy_dest_slice : 3; // +4
xenos::ColorFormat copy_dest_format : 6; // +7
xenos::SurfaceNumberFormat copy_dest_number : 3; // +13
int32_t copy_dest_exp_bias : 6; // +16
uint32_t : 2; // +22
uint32_t copy_dest_swap : 1; // +24
};
uint32_t value;
static constexpr Register register_index = XE_GPU_REG_RB_COPY_DEST_INFO;

View File

@@ -1342,7 +1342,7 @@ void RenderTargetCache::ChangeOwnership(
nullptr, resolve_clear_cutout)) {
RenderTargetKey transfer_host_depth_source =
host_depth_encoding_different
? it->second.host_depth_render_targets[dest.resource_format]
? it->second.GetHostDepthRenderTarget(dest.GetDepthFormat())
: RenderTargetKey();
if (transfer_host_depth_source == transfer_source) {
// Same render target, don't provide a separate host depth source.
@@ -1387,7 +1387,7 @@ void RenderTargetCache::ChangeOwnership(
// Claim the current range.
it->second.render_target = dest;
if (host_depth_encoding_different) {
it->second.host_depth_render_targets[dest.resource_format] = dest;
it->second.GetHostDepthRenderTarget(dest.GetDepthFormat()) = dest;
}
// Check if can merge with the next range after claiming.
std::map<uint32_t, OwnershipRange>::iterator it_next;

View File

@@ -538,13 +538,8 @@ class RenderTargetCache {
// float32 value to that of an unorm24 with a totally wrong value). If the
// range hasn't been used yet (render_target.IsEmpty() == true), these are
// empty too.
union {
struct {
RenderTargetKey host_depth_render_target_unorm24;
RenderTargetKey host_depth_render_target_float24;
};
RenderTargetKey host_depth_render_targets[2];
};
RenderTargetKey host_depth_render_target_unorm24;
RenderTargetKey host_depth_render_target_float24;
OwnershipRange(uint32_t end_tiles, RenderTargetKey render_target,
RenderTargetKey host_depth_render_target_unorm24,
RenderTargetKey host_depth_render_target_float24)
@@ -552,6 +547,22 @@ class RenderTargetCache {
render_target(render_target),
host_depth_render_target_unorm24(host_depth_render_target_unorm24),
host_depth_render_target_float24(host_depth_render_target_float24) {}
const RenderTargetKey& GetHostDepthRenderTarget(
xenos::DepthRenderTargetFormat resource_format) const {
assert_true(
resource_format == xenos::DepthRenderTargetFormat::kD24S8 ||
resource_format == xenos::DepthRenderTargetFormat::kD24FS8,
"Illegal resource format");
return resource_format == xenos::DepthRenderTargetFormat::kD24S8
? host_depth_render_target_unorm24
: host_depth_render_target_float24;
}
RenderTargetKey& GetHostDepthRenderTarget(
xenos::DepthRenderTargetFormat resource_format) {
return const_cast<RenderTargetKey&>(
const_cast<const OwnershipRange*>(this)->GetHostDepthRenderTarget(
resource_format));
}
bool IsOwnedBy(RenderTargetKey key,
bool host_depth_encoding_different) const {
if (render_target != key) {
@@ -561,7 +572,7 @@ class RenderTargetCache {
return false;
}
if (host_depth_encoding_different && !key.is_depth &&
host_depth_render_targets[key.resource_format] != key) {
GetHostDepthRenderTarget(key.GetDepthFormat()) != key) {
// Depth encoding is the same, but different addressing is needed.
return false;
}

View File

@@ -185,7 +185,7 @@ enum class IndexFormat : uint32_t {
};
// SurfaceNumberX from yamato_enum.h.
enum class SurfaceNumFormat : uint32_t {
enum class SurfaceNumberFormat : uint32_t {
kUnsignedRepeatingFraction = 0,
// Microsoft-style, scale factor (2^(n-1))-1.
kSignedRepeatingFraction = 1,
@@ -1176,14 +1176,120 @@ union alignas(uint32_t) xe_gpu_fetch_group_t {
};
static_assert_size(xe_gpu_fetch_group_t, sizeof(uint32_t) * 6);
// GPU_MEMEXPORT_STREAM_CONSTANT from a game .pdb - float constant for memexport
// stream configuration.
// This is used with the floating-point ALU in shaders (written to eA using
// mad), so the dwords have a normalized exponent when reinterpreted as floats
// (otherwise they would be flushed to zero), but actually these are packed
// integers. dword_1 specifically is 2^23 because
// powf(2.0f, 23.0f) + float(i) == 0x4B000000 | i
// so mad can pack indices as integers in the lower bits.
// Shader memory export (memexport) allows for writing of arbitrary formatted
// data with random access / scatter capabilities. It provides functionality
// largely similar to resolving - format packing, supporting arbitrary color
// formats, from sub-dword ones such as k_8 in 58410B86, to 128-bit ones, with
// endian swap similar to how it's performed in resolves (up to 128-bit);
// specifying the number format, swapping red and blue channels - though with no
// exponent biasing. Unlike resolving, however, instead of writing to tiled
// textures, it exports the data to up to 5 elements (the eM# shader registers,
// each corresponding to `base address + element size * (offset + 0...4)`) in a
// stream defined by a stream constant and an offset in elements written to eA -
// a shader, however, can write to multiple streams with different or the same
// stream constants, by performing `alloc export` multiple times. It's used
// mostly in vertex shaders (most commonly in improvised "compute shaders" done
// by executing a vertex shader for a number of point-type primitives covering
// nothing), though usage in pixel shaders is also possible - an example is
// provided in the "Advanced Screenspace Antialiasing" presentation by Arne
// Schober.
// https://ubm-twvideo01.s3.amazonaws.com/o1/vault/gdceurope2010/slides/A_Schober_Advanced_Screenspace_Antialiasing.pdf
//
// Unlike fetch constants, which are passed via special registers, a memory
// export stream is configured by writing the stream constant and the offset to
// a shader export register (eA) allocated by the shader - similar to more
// conventional exports like oPos, o#, oC#. Therefore, in general, it's not
// possible to know what its value will be without running the shader. For
// emulation, this means that the memory range referenced by an export - that
// needs to be validated - requires running the shader on the CPU in general.
// Thankfully, however, the usual way of setting up eA is by executing:
// `mad eA, r#, const0100, c#`
// where c# is the stream float4 constant from the float constant registers, and
// const0100 is a literal (0.0f, 1.0f, 0.0f, 0.0f) constant, also from the float
// constant registers, used for placing the element index (r#) in the correct
// component of eA. This allows for easy gathering of memexport stream
// constants, which contain both the base address and the size of the
// destination buffer for bounds checking, from the shader code and the float
// constant registers, as long as the guest uses this instruction pattern to
// write to eA.
//
// The Xenos doesn't have an integer ALU, and denormals are treated as zero and
// are flushed. However, eA contains integers and bit fields. A stream constant
// is thus structured in a way that allows for packing integers in normalized
// floating-point numbers.
//
// X contains the base address of the stream in dwords as integer bits in the
// lower 30 bits, and bits 0b01 in the top. The 0b01 bits make the exponent
// nonzero, so the number is considered normalized, and therefore isn't flushed
// to zero. With only 512 MB of the physical memory on the Xbox 360, the
// exponent can't become 0b11111111, so X also won't be NaN for any valid Xbox
// 360 physical address (though in general the GPU supports 32-bit addresses,
// but this is originally an Xbox 360-specific feature, that was later, however,
// likely reused for GL_QCOM_writeonly_rendering).
//
// TODO(Triang3l): Verify whether GL_QCOM_writeonly_rendering is actually
// memexport on the Adreno 2xx using GL_OES_get_program_binary - it's also
// interesting to see how alphatest interacts with it, whether it's still true
// fixed-function alphatest, as it's claimed to be supported as usual by the
// extension specification - it's likely, however, that memory exports are
// discarded alongside other exports such as oC# and oDepth this way.
//
// Y of eA contains the offset in elements - this is what shaders are supposed
// to calculate from something like the vertex index. Again, it's specified as
// an integer in the low bits, not as a truly floating-point number. For this
// purpose, stream constants contain the value 2^23 - when a whole
// floating-point number smaller than 2^23 is added as floating-point to 2^23,
// its integer representation becomes the mantissa bits of a number with an
// exponent of 23. Via multiply-add, `offset * 1.0f + exp2f(23)` is written here
// by the shader, allowing for element offsets of up to 2^23 - 1.
//
// Z is a bit field with the information about the formatting of the data. It's
// also packed as a normalized floating-point number, but in a cleaner way than
// X because not as many bits are required - just like Y, it has an exponent of
// 23 (possibly to let shaders build these values manually using floating-point
// multiply-add like integer shift-or, and finally to add 2^23, though that's
// not a case easy to handle in emulation, unlike prebuilt stream constants).
//
// W contains the number of elements in the stream. It's also packed with the
// full 23 exponent just like Y and Z, there's no way to index more than 2^23
// elements using packing via addition to 2^23, so this field also doesn't need
// more bits than that.
//
// Examples of setup in titles (Z from MSB to LSB):
//
// 4D5307E6 particles (different VS invocation counts, like 1, 2, 4):
// There is a passthrough shader - useful for verification as it simply writes
// directly what it reads via vfetch of various formats. Another shader (with
// different c# numbers, but same formats) does complicated math to process the
// particles.
// c152: Z = 010010110000|0|111|00|100110|00000|010, count = 35840
// 8in32, 32_32_32_32_FLOAT, float, RGBA - from 32_32_32_32_FLOAT vfetch
// c154, 162: Z = 010010110000|0|111|00|100000|00000|001, count = 71680
// 8in16, 16_16_16_16_FLOAT, float, RGBA - from 16_16_16_16_FLOAT vfetch
// c156, 158, 160: Z = 010010110000|0|000|00|011010|00000|001, count = 71680
// 8in16, 16_16_16_16, unorm, RGBA - from 16_16_16_16 unorm vfetch
// c164: Z = 010010110000|0|111|00|011111|00000|001, count = 143360
// 8in16, 16_16_FLOAT, float, RGBA - from 16_16_FLOAT vfetch
// c166: Z = 010010110000|0|000|00|011001|00000|001, count = 143360
// 8in16, 16_16, unorm, RGBA - from 16_16 unorm vfetch
// c168: Z = 010010110000|0|001|00|000111|00000|010, count = 143360
// 8in32, 2_10_10_10, snorm, RGBA - from 2_10_10_10 snorm vfetch
// c170, c172: Z = 010010110000|1|000|00|000110|00000|010, count = 143360
// 8in32, 8_8_8_8, unorm, BGRA - from 8_8_8_8 unorm vfetch with .zyxw swizzle
//
// 4D5307E6 water simulation (2048 VS invocations):
// c130: Z = 010010110000|0|111|00|100110|00000|010, count = 16384
// 8in32, 32_32_32_32_FLOAT, float, RGBA
// The shader has 5 memexports of this kind and 6 32_32_32_32_FLOAT vfetches.
//
// 4D5307E6 water tessellation factors (1 VS invocation per triangle patch):
// c130: Z = 010010110000|0|111|11|100100|11111|010, count = patch count * 3
// 8in32, 32_FLOAT, float, RGBA
//
// 41560817 texture memory copying (64 bytes per invocation, two eA, eight eM#):
// c0: Z = 010010110000|0|010|11|011010|00011|001
// 8in16, 16_16_16_16, uint, RGBA - from 16_16_16_16 uint vfetch
// (16_16_16_16 is the largest color format without special values)
union alignas(uint32_t) xe_gpu_memexport_stream_t {
struct {
uint32_t base_address : 30; // +0 dword_0 physical address >> 2
@@ -1191,13 +1297,13 @@ union alignas(uint32_t) xe_gpu_memexport_stream_t {
uint32_t const_0x4b000000; // +0 dword_1
Endian128 endianness : 3; // +0 dword_2
uint32_t unused_0 : 5; // +3
ColorFormat format : 6; // +8
uint32_t unused_1 : 2; // +14
SurfaceNumFormat num_format : 3; // +16
uint32_t red_blue_swap : 1; // +19
uint32_t const_0x4b0 : 12; // +20
Endian128 endianness : 3; // +0 dword_2
uint32_t unused_0 : 5; // +3
ColorFormat format : 6; // +8
uint32_t unused_1 : 2; // +14
SurfaceNumberFormat num_format : 3; // +16
uint32_t red_blue_swap : 1; // +19
uint32_t const_0x4b0 : 12; // +20
uint32_t index_count : 23; // +0 dword_3
uint32_t const_0x96 : 9; // +23

View File

@@ -52,7 +52,9 @@ KernelState::KernelState(Emulator* emulator)
user_profile_ = std::make_unique<xam::UserProfile>();
auto content_root = emulator_->content_root();
content_root = std::filesystem::absolute(content_root);
if (!content_root.empty()) {
content_root = std::filesystem::absolute(content_root);
}
content_manager_ = std::make_unique<xam::ContentManager>(this, content_root);
assert_null(shared_kernel_state_);

View File

@@ -290,6 +290,13 @@ int32_t format_core(PPCContext* ppc_context, FormatData& data, ArgList& args,
}
state = FS_Type;
continue;
} else if (c == 'L') {
// 58410826 incorrectly uses 'L' instead of 'l'.
// TODO(gibbed): L appears to be treated as an invalid token by
// xboxkrnl, investigate how invalid tokens are processed in xboxkrnl
// formatting when state FF_Type is reached.
state = FS_Type;
continue;
} else if (c == 'h') {
flags |= FF_IsShort;
state = FS_Type;

View File

@@ -14,3 +14,7 @@ project("xenia-ui")
local_platform_files()
removefiles({"*_demo.cc"})
removefiles({"windowed_app_main_*.cc"})
filter("platforms:Android-*")
-- Exports JNI functions.
wholelib("On")

View File

@@ -0,0 +1,25 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/windowed_app.h"
#include <string>
#include <unordered_map>
namespace xe {
namespace ui {
#if XE_UI_WINDOWED_APPS_IN_LIBRARY
// A zero-initialized pointer to remove dependence on the initialization order
// of the map relatively to the app creator proxies.
std::unordered_map<std::string, WindowedApp::Creator>* WindowedApp::creators_;
#endif // XE_UI_WINDOWED_APPS_IN_LIBRARY
} // namespace ui
} // namespace xe

View File

@@ -13,15 +13,17 @@
#include <cstddef>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/base/platform.h"
#include "xenia/ui/windowed_app_context.h"
#if XE_PLATFORM_ANDROID
#include <android/native_activity.h>
#include "xenia/ui/windowed_app_context_android.h"
// Multiple apps in a single library instead of separate executables.
#define XE_UI_WINDOWED_APPS_IN_LIBRARY 1
#endif
namespace xe {
@@ -36,6 +38,9 @@ class WindowedApp {
// initialization of platform-specific parts, should preferably be as simple
// as possible).
using Creator = std::unique_ptr<xe::ui::WindowedApp> (*)(
xe::ui::WindowedAppContext& app_context);
WindowedApp(const WindowedApp& app) = delete;
WindowedApp& operator=(const WindowedApp& app) = delete;
virtual ~WindowedApp() = default;
@@ -101,27 +106,67 @@ class WindowedApp {
std::string name_;
std::string positional_options_usage_;
std::vector<std::string> positional_options_;
#if XE_UI_WINDOWED_APPS_IN_LIBRARY
public:
class CreatorRegistration {
public:
CreatorRegistration(const std::string_view identifier, Creator creator) {
if (!creators_) {
// Will be deleted by the last creator registration's destructor, no
// need for a library destructor.
creators_ = new std::unordered_map<std::string, WindowedApp::Creator>;
}
iterator_inserted_ = creators_->emplace(identifier, creator);
assert_true(iterator_inserted_.second);
}
~CreatorRegistration() {
if (iterator_inserted_.second) {
creators_->erase(iterator_inserted_.first);
if (creators_->empty()) {
delete creators_;
}
}
}
private:
std::pair<std::unordered_map<std::string, Creator>::iterator, bool>
iterator_inserted_;
};
static Creator GetCreator(const std::string& identifier) {
if (!creators_) {
return nullptr;
}
auto it = creators_->find(identifier);
return it != creators_->end() ? it->second : nullptr;
}
private:
static std::unordered_map<std::string, Creator>* creators_;
#endif // XE_UI_WINDOWED_APPS_IN_LIBRARY
};
#if XE_PLATFORM_ANDROID
// Multiple apps in a single library. ANativeActivity_onCreate chosen via
// android.app.func_name of the NativeActivity of each app.
#define XE_DEFINE_WINDOWED_APP(export_name, creator) \
__attribute__((visibility("default"))) extern "C" void export_name( \
ANativeActivity* activity, void* saved_state, size_t saved_state_size) { \
xe::ui::AndroidWindowedAppContext::StartAppOnNativeActivityCreate( \
activity, saved_state, saved_state_size, creator); \
#if XE_UI_WINDOWED_APPS_IN_LIBRARY
// Multiple apps in a single library.
#define XE_DEFINE_WINDOWED_APP(identifier, creator) \
namespace xe { \
namespace ui { \
namespace windowed_app_creator_registrations { \
xe::ui::WindowedApp::CreatorRegistration identifier(#identifier, creator); \
} \
} \
}
#else
// Separate executables for each app.
std::unique_ptr<WindowedApp> (*GetWindowedAppCreator())(
WindowedAppContext& app_context);
#define XE_DEFINE_WINDOWED_APP(export_name, creator) \
std::unique_ptr<xe::ui::WindowedApp> (*xe::ui::GetWindowedAppCreator())( \
xe::ui::WindowedAppContext & app_context) { \
return creator; \
#define XE_DEFINE_WINDOWED_APP(identifier, creator) \
xe::ui::WindowedApp::Creator xe::ui::GetWindowedAppCreator() { \
return creator; \
}
#endif
#endif // XE_UI_WINDOWED_APPS_IN_LIBRARY
} // namespace ui
} // namespace xe

View File

@@ -9,63 +9,358 @@
#include "xenia/ui/windowed_app_context_android.h"
#include <android/asset_manager_jni.h>
#include <android/configuration.h>
#include <android/native_activity.h>
#include <android/log.h>
#include <android/looper.h>
#include <fcntl.h>
#include <jni.h>
#include <unistd.h>
#include <array>
#include <cstdint>
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/base/main_android.h"
#include "xenia/ui/windowed_app.h"
namespace xe {
namespace ui {
void AndroidWindowedAppContext::StartAppOnNativeActivityCreate(
ANativeActivity* activity, [[maybe_unused]] void* saved_state,
[[maybe_unused]] size_t saved_state_size,
std::unique_ptr<WindowedApp> (*app_creator)(
WindowedAppContext& app_context)) {
// TODO(Triang3l): Pass the launch options from the Intent or the saved
// instance state.
AndroidWindowedAppContext* app_context =
new AndroidWindowedAppContext(activity);
// The pointer is now held by the Activity as its ANativeActivity::instance,
// until the destruction.
if (!app_context->InitializeApp(app_creator)) {
delete app_context;
ANativeActivity_finish(activity);
}
}
AndroidWindowedAppContext::~AndroidWindowedAppContext() {
// TODO(Triang3l): Unregister activity callbacks.
activity_->instance = nullptr;
xe::ShutdownAndroidAppFromMainThread();
}
void AndroidWindowedAppContext::NotifyUILoopOfPendingFunctions() {
// TODO(Triang3l): Request message processing in the UI thread.
// Don't check ui_thread_looper_callback_registered_, as it's owned
// exclusively by the UI thread, while this may be called by any, and in case
// of a pipe error, the callback will be invoked by the looper, which will
// trigger all the necessary shutdown, and the pending functions will be
// called anyway by the shutdown.
UIThreadLooperCallbackCommand command =
UIThreadLooperCallbackCommand::kExecutePendingFunctions;
if (write(ui_thread_looper_callback_pipe_[1], &command, sizeof(command)) !=
sizeof(command)) {
XELOGE(
"AndroidWindowedAppContext: Failed to write a pending function "
"execution command to the UI thread looper callback pipe");
return;
}
ALooper_wake(ui_thread_looper_);
}
void AndroidWindowedAppContext::PlatformQuitFromUIThread() {
ANativeActivity_finish(activity_);
// All the shutdown will be done in onDestroy of the activity.
if (activity_ && activity_method_finish_) {
ui_thread_jni_env_->CallVoidMethod(activity_, activity_method_finish_);
}
}
AndroidWindowedAppContext::AndroidWindowedAppContext(ANativeActivity* activity)
: activity_(activity) {
int32_t api_level;
AndroidWindowedAppContext*
AndroidWindowedAppContext::JniActivityInitializeWindowedAppOnCreate(
JNIEnv* jni_env, jobject activity, jstring windowed_app_identifier,
jobject asset_manager) {
WindowedApp::Creator app_creator;
{
AConfiguration* configuration = AConfiguration_new();
AConfiguration_fromAssetManager(configuration, activity->assetManager);
api_level = AConfiguration_getSdkVersion(configuration);
AConfiguration_delete(configuration);
const char* windowed_app_identifier_c_str =
jni_env->GetStringUTFChars(windowed_app_identifier, nullptr);
if (!windowed_app_identifier_c_str) {
__android_log_write(
ANDROID_LOG_ERROR, "AndroidWindowedAppContext",
"Failed to get the UTF-8 string for the windowed app identifier");
return nullptr;
}
app_creator = WindowedApp::GetCreator(windowed_app_identifier_c_str);
if (!app_creator) {
__android_log_print(ANDROID_LOG_ERROR, "AndroidWindowedAppContext",
"Failed to get the creator for the windowed app %s",
windowed_app_identifier_c_str);
jni_env->ReleaseStringUTFChars(windowed_app_identifier,
windowed_app_identifier_c_str);
return nullptr;
}
jni_env->ReleaseStringUTFChars(windowed_app_identifier,
windowed_app_identifier_c_str);
}
xe::InitializeAndroidAppFromMainThread(api_level);
AndroidWindowedAppContext* app_context = new AndroidWindowedAppContext;
if (!app_context->Initialize(jni_env, activity, asset_manager)) {
delete app_context;
return nullptr;
}
activity_->instance = this;
// TODO(Triang3l): Register activity callbacks.
if (!app_context->InitializeApp(app_creator)) {
// InitializeApp might have sent commands to the UI thread looper callback
// pipe, perform deferred destruction.
app_context->RequestDestruction();
return nullptr;
}
return app_context;
}
void AndroidWindowedAppContext::JniActivityOnDestroy() {
if (app_) {
app_->InvokeOnDestroy();
app_.reset();
}
RequestDestruction();
}
AndroidWindowedAppContext::~AndroidWindowedAppContext() { Shutdown(); }
bool AndroidWindowedAppContext::Initialize(JNIEnv* ui_thread_jni_env,
jobject activity,
jobject asset_manager) {
// Xenia logging is not initialized yet - use __android_log_write or
// __android_log_print until InitializeAndroidAppFromMainThread is done.
ui_thread_jni_env_ = ui_thread_jni_env;
// Initialize the asset manager for retrieving the current configuration.
asset_manager_jobject_ = ui_thread_jni_env_->NewGlobalRef(asset_manager);
if (!asset_manager_jobject_) {
__android_log_write(
ANDROID_LOG_ERROR, "AndroidWindowedAppContext",
"Failed to create a global reference to the asset manager");
Shutdown();
return false;
}
asset_manager_ =
AAssetManager_fromJava(ui_thread_jni_env_, asset_manager_jobject_);
if (!asset_manager_) {
__android_log_write(ANDROID_LOG_ERROR, "AndroidWindowedAppContext",
"Failed to create get the AAssetManager");
Shutdown();
return false;
}
// Get the initial configuration.
configuration_ = AConfiguration_new();
if (!configuration_) {
__android_log_write(ANDROID_LOG_ERROR, "AndroidWindowedAppContext",
"Failed to create an AConfiguration");
Shutdown();
return false;
}
AConfiguration_fromAssetManager(configuration_, asset_manager_);
// Initialize Xenia globals that may depend on the API level, as well as
// logging.
xe::InitializeAndroidAppFromMainThread(
AConfiguration_getSdkVersion(configuration_));
android_base_initialized_ = true;
// Initialize interfacing with the WindowedAppActivity.
activity_ = ui_thread_jni_env_->NewGlobalRef(activity);
if (!activity_) {
XELOGE(
"AndroidWindowedAppContext: Failed to create a global reference to the "
"activity");
Shutdown();
return false;
}
{
jclass activity_class_local_ref =
ui_thread_jni_env_->GetObjectClass(activity);
if (!activity_class_local_ref) {
XELOGE("AndroidWindowedAppContext: Failed to get the activity class");
Shutdown();
return false;
}
activity_class_ = reinterpret_cast<jclass>(ui_thread_jni_env_->NewGlobalRef(
reinterpret_cast<jobject>(activity_class_local_ref)));
ui_thread_jni_env_->DeleteLocalRef(
reinterpret_cast<jobject>(activity_class_local_ref));
}
if (!activity_class_) {
XELOGE(
"AndroidWindowedAppContext: Failed to create a global reference to the "
"activity class");
Shutdown();
return false;
}
bool activity_ids_obtained = true;
activity_ids_obtained &=
(activity_method_finish_ = ui_thread_jni_env_->GetMethodID(
activity_class_, "finish", "()V")) != nullptr;
if (!activity_ids_obtained) {
XELOGE("AndroidWindowedAppContext: Failed to get the activity class IDs");
Shutdown();
return false;
}
// Initialize sending commands to the UI thread looper callback, for
// requesting function calls in the UI thread.
ui_thread_looper_ = ALooper_forThread();
// The context may be created only in the UI thread, which must have an
// internal looper.
assert_not_null(ui_thread_looper_);
if (!ui_thread_looper_) {
XELOGE("AndroidWindowedAppContext: Failed to get the UI thread looper");
Shutdown();
return false;
}
// The looper can be woken up by other threads, so acquiring it. Shutdown
// assumes that if ui_thread_looper_ is not null, it has been acquired.
ALooper_acquire(ui_thread_looper_);
if (pipe(ui_thread_looper_callback_pipe_.data())) {
XELOGE(
"AndroidWindowedAppContext: Failed to create the UI thread looper "
"callback pipe");
Shutdown();
return false;
}
if (ALooper_addFd(ui_thread_looper_, ui_thread_looper_callback_pipe_[0],
ALOOPER_POLL_CALLBACK, ALOOPER_EVENT_INPUT,
UIThreadLooperCallback, this) != 1) {
XELOGE(
"AndroidWindowedAppContext: Failed to add the callback to the UI "
"thread looper");
Shutdown();
return false;
}
ui_thread_looper_callback_registered_ = true;
return true;
}
void AndroidWindowedAppContext::Shutdown() {
if (app_) {
app_->InvokeOnDestroy();
app_.reset();
}
// The app should destroy the window, but make sure everything is cleaned up
// anyway.
assert_null(activity_window_);
activity_window_ = nullptr;
if (ui_thread_looper_callback_registered_) {
ALooper_removeFd(ui_thread_looper_, ui_thread_looper_callback_pipe_[0]);
ui_thread_looper_callback_registered_ = false;
}
for (int& pipe_fd : ui_thread_looper_callback_pipe_) {
if (pipe_fd == -1) {
continue;
}
close(pipe_fd);
pipe_fd = -1;
}
if (ui_thread_looper_) {
ALooper_release(ui_thread_looper_);
ui_thread_looper_ = nullptr;
}
activity_method_finish_ = nullptr;
if (activity_class_) {
ui_thread_jni_env_->DeleteGlobalRef(
reinterpret_cast<jobject>(activity_class_));
activity_class_ = nullptr;
}
if (activity_) {
ui_thread_jni_env_->DeleteGlobalRef(activity_);
activity_ = nullptr;
}
if (android_base_initialized_) {
xe::ShutdownAndroidAppFromMainThread();
android_base_initialized_ = false;
}
if (configuration_) {
AConfiguration_delete(configuration_);
configuration_ = nullptr;
}
asset_manager_ = nullptr;
if (asset_manager_jobject_) {
ui_thread_jni_env_->DeleteGlobalRef(asset_manager_jobject_);
asset_manager_jobject_ = nullptr;
}
ui_thread_jni_env_ = nullptr;
}
void AndroidWindowedAppContext::RequestDestruction() {
// According to ALooper_removeFd documentation:
// "...it is possible for the callback to already be running or for it to run
// one last time if the file descriptor was already signalled. Calling code
// is responsible for ensuring that this case is safely handled. For example,
// if the callback takes care of removing itself during its own execution
// either by returning 0 or by calling this method..."
// If the looper callback is registered, the pipe may have pending commands,
// and thus the callback may still be called with the pointer to the context
// as the user data.
if (!ui_thread_looper_callback_registered_) {
delete this;
return;
}
UIThreadLooperCallbackCommand command =
UIThreadLooperCallbackCommand::kDestroy;
if (write(ui_thread_looper_callback_pipe_[1], &command, sizeof(command)) !=
sizeof(command)) {
XELOGE(
"AndroidWindowedAppContext: Failed to write a destruction command to "
"the UI thread looper callback pipe");
delete this;
return;
}
ALooper_wake(ui_thread_looper_);
}
int AndroidWindowedAppContext::UIThreadLooperCallback(int fd, int events,
void* data) {
// In case of errors, destruction of the pipe (most importantly the write end)
// must not be done here immediately as other threads, which may still be
// sending commands, would not be aware of that.
auto app_context = static_cast<AndroidWindowedAppContext*>(data);
if (events &
(ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP | ALOOPER_EVENT_INVALID)) {
// Will return 0 to unregister self, this file descriptor is not usable
// anymore, so let everything potentially referencing it in QuitFromUIThread
// know.
app_context->ui_thread_looper_callback_registered_ = false;
XELOGE(
"AndroidWindowedAppContext: The UI thread looper callback pipe file "
"descriptor has encountered an error condition during polling");
app_context->QuitFromUIThread();
return 0;
}
if (!(events & ALOOPER_EVENT_INPUT)) {
// Spurious callback call. Need a non-empty pipe.
return 1;
}
// Process one command with a blocking `read`. The callback will be invoked
// again and again if there is still data after this read.
UIThreadLooperCallbackCommand command;
switch (read(fd, &command, sizeof(command))) {
case sizeof(command):
break;
case -1:
// Will return 0 to unregister self, this file descriptor is not usable
// anymore, so let everything potentially referencing it in
// QuitFromUIThread know.
app_context->ui_thread_looper_callback_registered_ = false;
XELOGE(
"AndroidWindowedAppContext: The UI thread looper callback pipe file "
"descriptor has encountered an error condition during reading");
app_context->QuitFromUIThread();
return 0;
default:
// Something like incomplete data - shouldn't be happening, but not a
// reported error.
return 1;
}
switch (command) {
case UIThreadLooperCallbackCommand::kDestroy:
// Final destruction requested. Will unregister self by returning 0, so
// set ui_thread_looper_callback_registered_ to false so Shutdown won't
// try to unregister it too.
app_context->ui_thread_looper_callback_registered_ = false;
delete app_context;
return 0;
case UIThreadLooperCallbackCommand::kExecutePendingFunctions:
app_context->ExecutePendingFunctionsFromUIThread();
break;
}
return 1;
}
bool AndroidWindowedAppContext::InitializeApp(std::unique_ptr<WindowedApp> (
@@ -82,3 +377,24 @@ bool AndroidWindowedAppContext::InitializeApp(std::unique_ptr<WindowedApp> (
} // namespace ui
} // namespace xe
extern "C" {
JNIEXPORT jlong JNICALL
Java_jp_xenia_emulator_WindowedAppActivity_initializeWindowedAppOnCreateNative(
JNIEnv* jni_env, jobject activity, jstring windowed_app_identifier,
jobject asset_manager) {
return reinterpret_cast<jlong>(
xe::ui::AndroidWindowedAppContext ::
JniActivityInitializeWindowedAppOnCreate(
jni_env, activity, windowed_app_identifier, asset_manager));
}
JNIEXPORT void JNICALL
Java_jp_xenia_emulator_WindowedAppActivity_onDestroyNative(
JNIEnv* jni_env, jobject activity, jlong app_context_ptr) {
reinterpret_cast<xe::ui::AndroidWindowedAppContext*>(app_context_ptr)
->JniActivityOnDestroy();
}
} // extern "C"

View File

@@ -10,7 +10,11 @@
#ifndef XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_
#define XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_
#include <android/native_activity.h>
#include <android/asset_manager.h>
#include <android/configuration.h>
#include <android/looper.h>
#include <jni.h>
#include <array>
#include <memory>
#include "xenia/ui/windowed_app_context.h"
@@ -23,17 +27,6 @@ class WindowedApp;
class AndroidWindowedAppContext final : public WindowedAppContext {
public:
// For calling from android.app.func_name exports.
static void StartAppOnNativeActivityCreate(
ANativeActivity* activity, void* saved_state, size_t saved_state_size,
std::unique_ptr<WindowedApp> (*app_creator)(
WindowedAppContext& app_context));
// Defined in the translation unit where WindowedApp is complete because of
// std::unique_ptr.
~AndroidWindowedAppContext();
ANativeActivity* activity() const { return activity_; }
WindowedApp* app() const { return app_.get(); }
void NotifyUILoopOfPendingFunctions() override;
@@ -47,22 +40,76 @@ class AndroidWindowedAppContext final : public WindowedAppContext {
AndroidWindow* GetActivityWindow() const { return activity_window_; }
void SetActivityWindow(AndroidWindow* window) { activity_window_ = window; }
// For calling from WindowedAppActivity native methods.
static AndroidWindowedAppContext* JniActivityInitializeWindowedAppOnCreate(
JNIEnv* jni_env, jobject activity, jstring windowed_app_identifier,
jobject asset_manager);
void JniActivityOnDestroy();
private:
explicit AndroidWindowedAppContext(ANativeActivity* activity);
enum class UIThreadLooperCallbackCommand : uint8_t {
kDestroy,
kExecutePendingFunctions,
};
AndroidWindowedAppContext() = default;
// Don't delete this object directly externally if successfully initialized as
// the looper may still execute the callback for pending commands after an
// external ALooper_removeFd, and the callback receives a pointer to the
// context - deletion must be deferred and done in the callback itself.
// Defined in the translation unit where WindowedApp is complete because of
// std::unique_ptr.
~AndroidWindowedAppContext();
bool Initialize(JNIEnv* ui_thread_jni_env, jobject activity,
jobject asset_manager);
void Shutdown();
// Call this function instead of deleting the object directly, so if needed,
// deletion will be deferred until the callback (receiving a pointer to the
// context) can no longer be executed by the looper (will be done inside the
// callback).
void RequestDestruction();
static int UIThreadLooperCallback(int fd, int events, void* data);
bool InitializeApp(std::unique_ptr<WindowedApp> (*app_creator)(
WindowedAppContext& app_context));
// TODO(Triang3l): Switch from ANativeActivity to the context itself being the
// object for communication with the Java code when NativeActivity isn't used
// anymore as its functionality is heavily limited.
ANativeActivity* activity_;
std::unique_ptr<WindowedApp> app_;
// Useful notes about JNI usage on Android within Xenia:
// - All static libraries defining JNI native functions must be linked to
// shared libraries via LOCAL_WHOLE_STATIC_LIBRARIES.
// - If method or field IDs are cached, a global reference to the class needs
// to be held - it prevents the class from being unloaded by the class
// loaders (in a way that would make the IDs invalid when it's reloaded).
// - GetStringUTFChars (UTF-8) returns null-terminated strings, GetStringChars
// (UTF-16) does not.
JNIEnv* ui_thread_jni_env_ = nullptr;
// The object reference must be held by the app according to
// AAssetManager_fromJava documentation.
jobject asset_manager_jobject_ = nullptr;
AAssetManager* asset_manager_ = nullptr;
AConfiguration* configuration_ = nullptr;
bool android_base_initialized_ = false;
jobject activity_ = nullptr;
jclass activity_class_ = nullptr;
jmethodID activity_method_finish_ = nullptr;
// May be read by non-UI threads in NotifyUILoopOfPendingFunctions.
ALooper* ui_thread_looper_ = nullptr;
// [1] (the write file descriptor) may be referenced as read-only by non-UI
// threads in NotifyUILoopOfPendingFunctions.
std::array<int, 2> ui_thread_looper_callback_pipe_{-1, -1};
bool ui_thread_looper_callback_registered_ = false;
AndroidWindow* activity_window_ = nullptr;
// TODO(Triang3l): The rest of the context, including quit handler (and the
// destructor) calling `finish` on the activity, UI looper notification
// posting, etc.
std::unique_ptr<WindowedApp> app_;
};
} // namespace ui

View File

@@ -22,14 +22,8 @@ GTKWindowedAppContext::~GTKWindowedAppContext() {
if (quit_idle_pending_) {
g_source_remove(quit_idle_pending_);
}
{
// Lock the mutex for a pending_functions_idle_pending_ access memory
// barrier, even though no other threads can access this object anymore.
std::lock_guard<std::mutex> pending_functions_idle_pending_lock(
pending_functions_idle_pending_mutex_);
if (pending_functions_idle_pending_) {
g_source_remove(pending_functions_idle_pending_);
}
if (pending_functions_idle_pending_) {
g_source_remove(pending_functions_idle_pending_);
}
}