mirror of
https://github.com/libretro/parallel-n64.git
synced 2026-11-07 12:37:50 -10:00
parallel-rdp: stop using the C++ runtime
parallel-RDP and its frontend glue no longer call operator new or delete, register a destructor with __cxa_atexit, or carry type_info. - Objects live on the C heap. Util::create and Util::destroy do malloc/free with the constructor and destructor run in place. A type aligned past 8 bytes comes from memalign. heap_new aborts on out of memory, as operator new does, and is out of line so a call site stays one call. A destructor that does work runs out of line once per type, as a deleting destructor does. - Virtual interfaces become structs of a function pointer and a user pointer: the logging hook, Vulkan::DeviceFactory, DebugChannelInterface and the renderer's ValidationInterface. Renderer and VideoInterface keep their debug channel as a member, so they have no vtable left. Crash reports go through one cold, out-of-line report_rdp_crash. - rdp.cpp keeps its static timestamp and image handles in Util::HeldHandle, which has no destructor. They were already dropped by hand while the device is up, and now nothing runs at exit, after the device is gone. Assigning a handle drops the old reference before storing the new one, so a temporary handed in is known empty when it dies. - free_allocators stays out of line and drops the slot stores that num_allocators already makes dead. - parallel.cpp and parallel_hle.cpp build without exceptions, as the rest of the renderer does. Nothing there throws, and sanitizer builds otherwise give them landing pads. GCC 13 x86-64 -O2 against master, parallel-RDP objects and rdp.o: - 95091 -> 95024 instructions, text -351 bytes, data -372 bytes. - Hot paths: load_tile -7 and validate_draw_state -30 instructions (its crash report moved out of line), complete_frame -9, deinit -12. begin_frame and the other per-frame handle code are unchanged. tools/ci/check_cxx_runtime.py, run in the parallel-RDP CI job, fails if any object under mupen64plus-video-paraLLEl references the runtime again, or defines a vtable or type_info. It finds nine such objects on master.
This commit is contained in:
1 parent
080fef16de
commit
fca2a07806
26 files changed
+365
-120
No files matched your search
@@ -163,6 +163,7 @@ test-parallel-rdp-linux-x64:
|
||||
- make -j$(nproc) platform=unix WITH_DYNAREC=$WITH_DYNAREC HAVE_THR_AL=$HAVE_THR_AL HAVE_PARALLEL=$HAVE_PARALLEL HAVE_PARALLEL_RSP=$HAVE_PARALLEL_RSP
|
||||
- make platform=unix HAVE_PARALLEL=$HAVE_PARALLEL lrhost_vk
|
||||
- python3 tools/rdp_no_storage_8bit_bank.py --check
|
||||
- python3 tools/ci/check_cxx_runtime.py
|
||||
- python3 tools/gen_upscaler_shaders.py --check
|
||||
- python3 tools/ci/parallel_rdp_regress.py --core ./parallel_n64_libretro.so --host ./lrhost_vk --out parallel-rdp-artifacts
|
||||
- python3 tools/ci/parallel_rdp_syncval.py --core ./parallel_n64_libretro.so --host ./lrhost_vk
|
||||
|
||||
+2
-1
@@ -650,7 +650,8 @@ ifeq ($(HAVE_PARALLEL),1)
|
||||
INCFLAGS += $(PARALLEL_RDP_INCLUDE_DIRS)
|
||||
SOURCES_CXX += $(PARALLEL_RDP_SOURCES_CXX) $(VIDEODIR_PARALLEL)/parallel.cpp $(VIDEODIR_PARALLEL)/rdp.cpp $(VIDEODIR_PARALLEL)/parallel_hle.cpp
|
||||
# Nothing there throws, catches or uses the standard library
|
||||
PARALLEL_RDP_NOEXCEPT_OBJECTS := $(PARALLEL_RDP_SOURCES_CXX:.cpp=.o) $(VIDEODIR_PARALLEL)/rdp.o
|
||||
PARALLEL_RDP_NOEXCEPT_OBJECTS := $(PARALLEL_RDP_SOURCES_CXX:.cpp=.o) $(VIDEODIR_PARALLEL)/rdp.o \
|
||||
$(VIDEODIR_PARALLEL)/parallel.o $(VIDEODIR_PARALLEL)/parallel_hle.o
|
||||
SOURCES_C += $(PARALLEL_RDP_SOURCES_C)
|
||||
LDFLAGS += $(PARALLEL_RDP_LDFLAGS)
|
||||
HAVE_RDP_EMIT := 1
|
||||
|
||||
@@ -60,7 +60,7 @@ static ShaderBank *create_shader_bank(Vulkan::Device &device, Vulkan::ResourceLa
|
||||
const VideoInterface &vi, const Renderer &renderer)
|
||||
{
|
||||
ShaderDefineResolver resolver = { &vi, &renderer };
|
||||
return new ShaderBank(device, layout, resolver);
|
||||
return Util::create<ShaderBank>(device, layout, resolver);
|
||||
}
|
||||
|
||||
namespace
|
||||
@@ -96,11 +96,11 @@ CommandProcessor::CommandProcessor(Vulkan::Device &device_, void *rdram_ptr,
|
||||
|
||||
if (const char *env = getenv("PARALLEL_RDP_DUMP_PATH"))
|
||||
{
|
||||
dump_writer = new RDPDumpWriter;
|
||||
dump_writer = Util::create<RDPDumpWriter>();
|
||||
if (!dump_writer->init(env, rdram_size, hidden_rdram_size))
|
||||
{
|
||||
LOGE("Failed to init RDP dump: %s.\n", env);
|
||||
delete dump_writer;
|
||||
Util::destroy(dump_writer);
|
||||
dump_writer = nullptr;
|
||||
}
|
||||
else
|
||||
@@ -209,7 +209,7 @@ CommandProcessor::CommandProcessor(Vulkan::Device &device_, void *rdram_ptr,
|
||||
// Here, where ShaderBank is complete.
|
||||
CommandProcessor::ShaderBankOwner::~ShaderBankOwner()
|
||||
{
|
||||
delete bank;
|
||||
Util::destroy(bank);
|
||||
}
|
||||
|
||||
CommandProcessor::~CommandProcessor()
|
||||
@@ -220,7 +220,7 @@ CommandProcessor::~CommandProcessor()
|
||||
// destroyed before the ring would otherwise be.
|
||||
ring.teardown_thread();
|
||||
rdp_upscaler_free(upscaler);
|
||||
delete dump_writer;
|
||||
Util::destroy(dump_writer);
|
||||
}
|
||||
|
||||
void CommandProcessor::begin_frame_context()
|
||||
@@ -1486,7 +1486,7 @@ CommandProcessor::CoherencyNodePool::~CoherencyNodePool()
|
||||
auto *node = static_cast<CoherencyNode *>(lists[i]);
|
||||
lists[i] = node->next;
|
||||
node->~CoherencyNode();
|
||||
::operator delete(node);
|
||||
free(node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1648,7 +1648,7 @@ static CoherencyNode *take_coherency_node(mpsc_stack_node_t **spare, mpsc_stack_
|
||||
unsigned capacity = 8;
|
||||
while (capacity < n)
|
||||
capacity <<= 1;
|
||||
void *mem = ::operator new(sizeof(CoherencyNode) + capacity * sizeof(CoherencyCopy));
|
||||
void *mem = Util::heap_new(sizeof(CoherencyNode) + capacity * sizeof(CoherencyCopy));
|
||||
auto *node = UTIL_PLACEMENT_NEW(mem) CoherencyNode;
|
||||
node->capacity = capacity;
|
||||
return node;
|
||||
|
||||
@@ -1587,32 +1587,41 @@ void Renderer::fixup_triangle_setup(TriangleSetup &setup) const
|
||||
setup.flags |= TRIANGLE_SETUP_FILL_COPY_RASTER_BIT;
|
||||
}
|
||||
|
||||
// Out of line and cold: draws and loads pay only the test of validation_iface.
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((noinline, cold))
|
||||
#endif
|
||||
void Renderer::report_rdp_crash(ValidationError err, const char *msg) const
|
||||
{
|
||||
validation_iface->report_rdp_crash(validation_iface->user, err, msg);
|
||||
}
|
||||
|
||||
void Renderer::validate_draw_state() const
|
||||
{
|
||||
if ((stream.static_raster_state.flags & RASTERIZATION_FILL_BIT) != 0)
|
||||
{
|
||||
if (fb.fmt == FBFormat::I4)
|
||||
{
|
||||
validation_iface->report_rdp_crash(ValidationError::Fill4bpp,
|
||||
report_rdp_crash(ValidationError::Fill4bpp,
|
||||
"Attempted to use Fill mode on 4bpp surface.");
|
||||
}
|
||||
|
||||
if ((stream.depth_blend_state.flags & DEPTH_BLEND_DEPTH_TEST_BIT) != 0)
|
||||
{
|
||||
validation_iface->report_rdp_crash(ValidationError::FillDepthTest,
|
||||
report_rdp_crash(ValidationError::FillDepthTest,
|
||||
"Attempted to use Fill mode with depth test.");
|
||||
}
|
||||
|
||||
if ((stream.depth_blend_state.flags & DEPTH_BLEND_IMAGE_READ_ENABLE_BIT) != 0)
|
||||
{
|
||||
validation_iface->report_rdp_crash(ValidationError::FillImageReadEnable,
|
||||
report_rdp_crash(ValidationError::FillImageReadEnable,
|
||||
"Attempted to use Fill mode with image read enable.");
|
||||
}
|
||||
|
||||
if ((stream.depth_blend_state.flags & DEPTH_BLEND_DEPTH_UPDATE_BIT) != 0 &&
|
||||
!constants.use_prim_depth)
|
||||
{
|
||||
validation_iface->report_rdp_crash(ValidationError::FillDepthWrite,
|
||||
report_rdp_crash(ValidationError::FillDepthWrite,
|
||||
"Attempted to use Fill mode with depth write enabled.");
|
||||
}
|
||||
}
|
||||
@@ -1620,7 +1629,7 @@ void Renderer::validate_draw_state() const
|
||||
{
|
||||
if (fb.fmt == FBFormat::RGBA8888)
|
||||
{
|
||||
validation_iface->report_rdp_crash(ValidationError::Copy32bpp,
|
||||
report_rdp_crash(ValidationError::Copy32bpp,
|
||||
"Attempted to use Copy mode on 32bpp surface.");
|
||||
}
|
||||
}
|
||||
@@ -1962,7 +1971,7 @@ void Renderer::submit_rasterization(Vulkan::CommandBuffer &cmd, Vulkan::Buffer &
|
||||
cmd.extract_pipeline_state(compile);
|
||||
if (pending_async_pipeline_insert(compile.hash))
|
||||
{
|
||||
auto *node = new PipelineNode;
|
||||
auto *node = Util::create<PipelineNode>();
|
||||
node->compile = rdp_move(compile);
|
||||
node->kind = PIPELINE_RECORD_COMPUTE;
|
||||
node->ahead = 0;
|
||||
@@ -2341,7 +2350,7 @@ void Renderer::submit_render_pass(Vulkan::CommandBuffer &cmd)
|
||||
render_pass_start = cmd.write_timestamp(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
||||
|
||||
if (debug_channel)
|
||||
cmd.begin_debug_channel(this, "Debug", 16 * 1024 * 1024);
|
||||
cmd.begin_debug_channel(&debug_channel_iface, "Debug", 16 * 1024 * 1024);
|
||||
|
||||
// Here we run 3 dispatches in parallel. Span setup and TMEM instances are low occupancy kind of jobs, but the binning
|
||||
// pass should dominate here unless the workload is trivial.
|
||||
@@ -3276,7 +3285,7 @@ void Renderer::load_tile(uint32_t tile, const LoadTileInfo &info)
|
||||
{
|
||||
if ((info.thi >> 2) > (info.tlo >> 2))
|
||||
{
|
||||
validation_iface->report_rdp_crash(ValidationError::InvalidMultilineLoadTlut,
|
||||
report_rdp_crash(ValidationError::InvalidMultilineLoadTlut,
|
||||
"Attempting to load multiple lines in TLUT.");
|
||||
}
|
||||
}
|
||||
@@ -3413,7 +3422,7 @@ void Renderer::load_tile_iteration(uint32_t tile, const LoadTileInfo &info, uint
|
||||
{
|
||||
LOGE("4-bit VRAM pointer crashes the RDP.\n");
|
||||
if (validation_iface)
|
||||
validation_iface->report_rdp_crash(ValidationError::LoadTile4bpp, "4-bit VRAM pointer crashes the RDP.");
|
||||
report_rdp_crash(ValidationError::LoadTile4bpp, "4-bit VRAM pointer crashes the RDP.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3773,7 +3782,7 @@ void Renderer::pipeline_work(void *user, mpsc_stack_node_t *item)
|
||||
if (node->urgent)
|
||||
retro_atomic_fetch_add_int(&renderer->pipeline_ahead_urgent_done, 1);
|
||||
if (!node->ahead)
|
||||
delete node;
|
||||
Util::destroy(node);
|
||||
}
|
||||
|
||||
void Renderer::build_pipelines_ahead(const pipeline_record_t *records, size_t count)
|
||||
|
||||
@@ -87,18 +87,17 @@ enum class ValidationError
|
||||
Copy32bpp
|
||||
};
|
||||
|
||||
class ValidationInterface
|
||||
struct ValidationInterface
|
||||
{
|
||||
public:
|
||||
virtual ~ValidationInterface() = default;
|
||||
// Validation errors may be called from a thread as errors are encountered.
|
||||
// Reports situations that would cause fatal error on a real RDP.
|
||||
// We only opt to report these situations rather than deliberately crashing the renderer.
|
||||
// Handling crashes is only relevant during development of N64 homebrew.
|
||||
virtual void report_rdp_crash(ValidationError err, const char *msg) = 0;
|
||||
void (*report_rdp_crash)(void *user, ValidationError err, const char *msg);
|
||||
void *user;
|
||||
};
|
||||
|
||||
class Renderer : public Vulkan::DebugChannelInterface
|
||||
class Renderer
|
||||
{
|
||||
public:
|
||||
explicit Renderer(CommandProcessor &processor);
|
||||
@@ -176,6 +175,7 @@ private:
|
||||
Vulkan::Device *device = nullptr;
|
||||
Vulkan::Buffer *rdram = nullptr;
|
||||
ValidationInterface *validation_iface = nullptr;
|
||||
void report_rdp_crash(ValidationError err, const char *msg) const;
|
||||
|
||||
Vulkan::BufferHandle upscaling_reference_rdram;
|
||||
Vulkan::BufferHandle upscaling_multisampled_rdram;
|
||||
@@ -388,7 +388,13 @@ private:
|
||||
|
||||
void message(const char *tag, uint32_t code,
|
||||
uint32_t x, uint32_t y, uint32_t z,
|
||||
uint32_t num_words, const Vulkan::DebugChannelInterface::Word *words) override;
|
||||
uint32_t num_words, const Vulkan::DebugChannelInterface::Word *words);
|
||||
static void debug_channel_message(void *user, const char *tag, uint32_t code,
|
||||
uint32_t x, uint32_t y, uint32_t z,
|
||||
uint32_t num_words, const Vulkan::DebugChannelInterface::Word *words)
|
||||
{
|
||||
static_cast<Renderer *>(user)->message(tag, code, x, y, z, num_words, words);
|
||||
}
|
||||
|
||||
bool can_support_minimum_subgroup_size(unsigned size) const;
|
||||
bool supports_subgroup_size_control(uint32_t minimum_size, uint32_t maximum_size) const;
|
||||
@@ -455,5 +461,8 @@ private:
|
||||
retro_atomic_int_t active_submissions;
|
||||
void enqueue_fence_wait(Vulkan::Fence fence);
|
||||
uint64_t last_submit_ns = 0;
|
||||
|
||||
// last: it is read only with a debug channel on
|
||||
Vulkan::DebugChannelInterface debug_channel_iface = { debug_channel_message, this };
|
||||
};
|
||||
}
|
||||
@@ -1519,7 +1519,7 @@ Vulkan::ImageHandle VideoInterface::scanout(VkImageLayout target_layout, const S
|
||||
auto cmd = device->request_command_buffer();
|
||||
|
||||
if (debug_channel)
|
||||
cmd->begin_debug_channel(this, "VI", 32 * 1024 * 1024);
|
||||
cmd->begin_debug_channel(&debug_channel_iface, "VI", 32 * 1024 * 1024);
|
||||
|
||||
// In the first pass, we need to read from VRAM and apply the fetch filter.
|
||||
// This is either the AA filter if coverage < 7, or the dither reconstruction filter if coverage == 7 and enabled.
|
||||
|
||||
@@ -103,7 +103,7 @@ struct VIScanoutBuffer
|
||||
|
||||
class Renderer;
|
||||
|
||||
class VideoInterface : public Vulkan::DebugChannelInterface
|
||||
class VideoInterface
|
||||
{
|
||||
public:
|
||||
void set_device(Vulkan::Device *device);
|
||||
@@ -180,7 +180,13 @@ private:
|
||||
|
||||
void message(const char *tag, uint32_t code,
|
||||
uint32_t x, uint32_t y, uint32_t z,
|
||||
uint32_t num_words, const Vulkan::DebugChannelInterface::Word *words) override;
|
||||
uint32_t num_words, const Vulkan::DebugChannelInterface::Word *words);
|
||||
static void debug_channel_message(void *user, const char *tag, uint32_t code,
|
||||
uint32_t x, uint32_t y, uint32_t z,
|
||||
uint32_t num_words, const Vulkan::DebugChannelInterface::Word *words)
|
||||
{
|
||||
static_cast<VideoInterface *>(user)->message(tag, code, x, y, z, num_words, words);
|
||||
}
|
||||
|
||||
// Intermediate images, reused instead of created every frame.
|
||||
mutable VIImagePool image_pool;
|
||||
@@ -309,5 +315,8 @@ private:
|
||||
unsigned scaling_factor, bool field_select,
|
||||
const ScanoutOptions &options) const;
|
||||
static bool need_fetch_bug_emulation(const Registers ®, unsigned scaling_factor);
|
||||
|
||||
// last: it is read only with a debug channel on
|
||||
Vulkan::DebugChannelInterface debug_channel_iface = { debug_channel_message, this };
|
||||
};
|
||||
}
|
||||
@@ -56,6 +56,23 @@ void *memalign_alloc(size_t boundary, size_t size)
|
||||
return (void *) addr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void *heap_new(size_t size)
|
||||
{
|
||||
void *ptr = malloc(size);
|
||||
if (!ptr)
|
||||
abort();
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void *heap_new_aligned(size_t size, size_t align)
|
||||
{
|
||||
void *ptr = memalign_alloc(align, size);
|
||||
if (!ptr)
|
||||
abort();
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void memalign_free(void *ptr)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
|
||||
@@ -32,31 +32,4 @@ void memalign_free(void *ptr);
|
||||
|
||||
struct AlignedDeleter { void operator()(void *ptr) { memalign_free(ptr); }};
|
||||
|
||||
template <typename T>
|
||||
struct AlignedAllocation
|
||||
{
|
||||
static void *operator new(size_t size)
|
||||
{
|
||||
void *ret = ::Util::memalign_alloc(alignof(T), size);
|
||||
if (!ret) abort();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void *operator new[](size_t size)
|
||||
{
|
||||
void *ret = ::Util::memalign_alloc(alignof(T), size);
|
||||
if (!ret) abort();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void operator delete(void *ptr)
|
||||
{
|
||||
return ::Util::memalign_free(ptr);
|
||||
}
|
||||
|
||||
static void operator delete[](void *ptr)
|
||||
{
|
||||
return ::Util::memalign_free(ptr);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -295,6 +295,58 @@ private:
|
||||
T *data = nullptr;
|
||||
};
|
||||
|
||||
// A reference held without a destructor, for objects of static storage:
|
||||
// they are dropped by hand while the device is up, rather than by a
|
||||
// destructor registered to run at exit, after it is gone. Zero is empty.
|
||||
template <typename T>
|
||||
struct HeldHandle
|
||||
{
|
||||
T *data;
|
||||
|
||||
// The reference goes first, and the slot is written after it,
|
||||
// so a temporary handed in is known empty when it dies.
|
||||
static void drop(T *object)
|
||||
{
|
||||
IntrusivePtr<T> last(object);
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
drop(data);
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
HeldHandle &operator=(const IntrusivePtr<T> &handle)
|
||||
{
|
||||
IntrusivePtr<T> keep(handle);
|
||||
drop(data);
|
||||
data = keep.release();
|
||||
return *this;
|
||||
}
|
||||
|
||||
HeldHandle &operator=(IntrusivePtr<T> &&handle)
|
||||
{
|
||||
drop(data);
|
||||
data = handle.release();
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool() const
|
||||
{
|
||||
return data != nullptr;
|
||||
}
|
||||
|
||||
// a handle of its own to it
|
||||
operator IntrusivePtr<T>() const
|
||||
{
|
||||
IntrusivePtr<T> handle;
|
||||
IntrusivePtr<T> borrowed(data);
|
||||
handle = borrowed;
|
||||
borrowed.release();
|
||||
return handle;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Deleter, typename ReferenceOps>
|
||||
IntrusivePtr<T> IntrusivePtrEnabled<T, Deleter, ReferenceOps>::reference_from_this()
|
||||
{
|
||||
@@ -308,13 +360,13 @@ using DerivedIntrusivePtrType = IntrusivePtr<Derived>;
|
||||
template <typename T, typename... P>
|
||||
DerivedIntrusivePtrType<T> make_handle(P &&... p)
|
||||
{
|
||||
return DerivedIntrusivePtrType<T>(new T(Util::forward<P>(p)...));
|
||||
return DerivedIntrusivePtrType<T>(Util::create_init<T>(Util::forward<P>(p)...));
|
||||
}
|
||||
|
||||
template <typename Base, typename Derived, typename... P>
|
||||
typename Base::IntrusivePtrType make_derived_handle(P &&... p)
|
||||
{
|
||||
return typename Base::IntrusivePtrType(new Derived(Util::forward<P>(p)...));
|
||||
return typename Base::IntrusivePtrType(Util::create_init<Derived>(Util::forward<P>(p)...));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
||||
@@ -509,7 +509,7 @@ public:
|
||||
T *to_delete = read_only.insert_yield(to_move);
|
||||
if (to_delete)
|
||||
object_pool.free(to_delete);
|
||||
delete l;
|
||||
Util::destroy(l);
|
||||
l = next;
|
||||
}
|
||||
}
|
||||
@@ -548,7 +548,7 @@ public:
|
||||
{
|
||||
Link *next = static_cast<Link *>(retro_atomic_load_relaxed_ptr(&l->next));
|
||||
object_pool.free(l->value);
|
||||
delete l;
|
||||
Util::destroy(l);
|
||||
l = next;
|
||||
}
|
||||
}
|
||||
@@ -569,7 +569,7 @@ public:
|
||||
{
|
||||
static_cast<IntrusiveHashMapEnabled<T> *>(value)->set_hash(hash);
|
||||
|
||||
Link *link = new Link;
|
||||
Link *link = Util::create<Link>();
|
||||
link->hash = hash;
|
||||
link->value = value;
|
||||
|
||||
@@ -584,7 +584,7 @@ public:
|
||||
{
|
||||
if (l->hash == hash)
|
||||
{
|
||||
delete link;
|
||||
Util::destroy(link);
|
||||
object_pool.free(value);
|
||||
return l->value;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ static bool log_helper(const char *tag, const char *fmt, va_list va)
|
||||
|
||||
namespace Util
|
||||
{
|
||||
static thread_local LoggingInterface *logging_iface;
|
||||
static thread_local LoggingInterface logging_iface;
|
||||
|
||||
bool interface_log(const char *tag, const char *fmt, ...)
|
||||
{
|
||||
@@ -55,7 +55,7 @@ bool interface_log(const char *tag, const char *fmt, ...)
|
||||
#ifdef __LIBRETRO__
|
||||
bool ret = log_helper(tag, fmt, va);
|
||||
#else
|
||||
bool ret = logging_iface->log(tag, fmt, va);
|
||||
bool ret = logging_iface(tag, fmt, va);
|
||||
#endif
|
||||
va_end(va);
|
||||
return ret;
|
||||
|
||||
@@ -28,12 +28,8 @@
|
||||
|
||||
namespace Util
|
||||
{
|
||||
class LoggingInterface
|
||||
{
|
||||
public:
|
||||
virtual ~LoggingInterface() = default;
|
||||
virtual bool log(const char *tag, const char *fmt, va_list va) = 0;
|
||||
};
|
||||
// Where a thread's log lines go, when not to the libretro log
|
||||
typedef bool (*LoggingInterface)(const char *tag, const char *fmt, va_list va);
|
||||
|
||||
bool interface_log(const char *tag, const char *fmt, ...);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public:
|
||||
UTIL_PLACEMENT_NEW(ptr) T(Util::forward<P>(p)...);
|
||||
return ptr;
|
||||
#else
|
||||
return new T(Util::forward<P>(p)...);
|
||||
return Util::create_init<T>(Util::forward<P>(p)...);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public:
|
||||
ptr->~T();
|
||||
vacants[num_vacants++] = ptr;
|
||||
#else
|
||||
delete ptr;
|
||||
Util::destroy(ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ public:
|
||||
return nullptr;
|
||||
return ObjectPool<T>::construct_at(slot(idx), Util::forward<P>(p)...);
|
||||
#else
|
||||
return ObjectPool<T>::construct_at(::operator new(sizeof(T)), Util::forward<P>(p)...);
|
||||
return ObjectPool<T>::construct_at(Util::heap_alloc<T>(sizeof(T)), Util::forward<P>(p)...);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ public:
|
||||
push_chain(index_of(ptr), index_of(ptr));
|
||||
#else
|
||||
ObjectPool<T>::destroy(ptr);
|
||||
::operator delete(ptr);
|
||||
Util::heap_free<T>(ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "logging.hpp"
|
||||
#include "timeline_trace_file.hpp"
|
||||
#include "util_lite.hpp"
|
||||
#include "thread_name.hpp"
|
||||
#include "timer.hpp"
|
||||
#include <string.h>
|
||||
@@ -44,7 +45,7 @@ void TimelineTraceFile::Event::set_tid(const char *tid_)
|
||||
|
||||
TimelineTraceFile::Event *TimelineTraceFile::allocate_event()
|
||||
{
|
||||
auto *e = new Event;
|
||||
auto *e = Util::create<Event>();
|
||||
e->next = nullptr;
|
||||
e->desc[0] = '\0';
|
||||
e->tid[0] = '\0';
|
||||
@@ -59,7 +60,7 @@ void TimelineTraceFile::submit_event(Event *e)
|
||||
if (worker)
|
||||
rdp_worker_push(worker, e);
|
||||
else
|
||||
delete e;
|
||||
Util::destroy(e);
|
||||
}
|
||||
|
||||
void TimelineTraceFile::write_event(void *self_, mpsc_stack_node_t *item)
|
||||
@@ -78,7 +79,7 @@ void TimelineTraceFile::write_event(void *self_, mpsc_stack_node_t *item)
|
||||
e->desc, e->tid, e->pid, end_us);
|
||||
}
|
||||
|
||||
delete e;
|
||||
Util::destroy(e);
|
||||
}
|
||||
|
||||
static void name_io_thread(void *)
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
@@ -88,12 +89,16 @@ static inline void copy_string(char *dst, size_t size, const char *src)
|
||||
dst[i] = '\0';
|
||||
}
|
||||
|
||||
/* below: delete on the C heap */
|
||||
template <typename T>
|
||||
static inline void destroy(T *t);
|
||||
|
||||
template <typename T>
|
||||
struct DefaultDelete
|
||||
{
|
||||
void operator()(T *t) const
|
||||
{
|
||||
delete t;
|
||||
destroy(t);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -138,6 +143,123 @@ inline void operator delete(void *, Util::PlacementTag, void *) noexcept
|
||||
|
||||
#define UTIL_PLACEMENT_NEW(p) new (::Util::PlacementTag(), (p))
|
||||
|
||||
/* Objects on the C heap: malloc and free, with the constructor and the
|
||||
destructor run in place, so nothing calls the C++ runtime's operator
|
||||
new or delete. A type aligned past 8 bytes, which malloc does not
|
||||
promise everywhere (32-bit MSVC), comes from memalign. */
|
||||
namespace Util
|
||||
{
|
||||
void *memalign_alloc(size_t boundary, size_t size);
|
||||
void memalign_free(void *ptr);
|
||||
|
||||
/* Clang and MSVC name it as the standard trait does; GCC up to 13 has
|
||||
only the older builtin, which Clang now warns of. */
|
||||
#if defined(__clang__) || defined(_MSC_VER)
|
||||
#define UTIL_IS_TRIVIALLY_DESTRUCTIBLE(T) __is_trivially_destructible(T)
|
||||
#else
|
||||
#define UTIL_IS_TRIVIALLY_DESTRUCTIBLE(T) __has_trivial_destructor(T)
|
||||
#endif
|
||||
|
||||
/* What a new-expression's operator new does: never NULL, out of memory
|
||||
ends the program. Out of line, as operator new is, so a call site is a
|
||||
call and nothing else. */
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define UTIL_HEAP_NEW_ATTRS __attribute__((malloc, returns_nonnull, alloc_size(1)))
|
||||
#else
|
||||
#define UTIL_HEAP_NEW_ATTRS
|
||||
#endif
|
||||
UTIL_HEAP_NEW_ATTRS void *heap_new(size_t size);
|
||||
/* for types aligned past what malloc promises */
|
||||
UTIL_HEAP_NEW_ATTRS void *heap_new_aligned(size_t size, size_t align);
|
||||
|
||||
template <typename T>
|
||||
static inline void *heap_alloc(size_t size)
|
||||
{
|
||||
return alignof(T) > 8 ? heap_new_aligned(size, alignof(T)) : heap_new(size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline void heap_free(void *p)
|
||||
{
|
||||
if (alignof(T) > 8)
|
||||
memalign_free(p);
|
||||
else
|
||||
free(p);
|
||||
}
|
||||
|
||||
/* new T(p...) */
|
||||
template <typename T, typename P0, typename... P>
|
||||
static inline T *create(P0 &&p0, P &&... p)
|
||||
{
|
||||
void *mem = heap_alloc<T>(sizeof(T));
|
||||
return UTIL_PLACEMENT_NEW(mem) T(forward<P0>(p0), forward<P>(p)...);
|
||||
}
|
||||
|
||||
/* new T(p...) for any number of arguments: with none, T() (zeroed where T
|
||||
has no constructor of its own), as a template forwarding a pack spells it */
|
||||
template <typename T, typename... P>
|
||||
static inline T *create_init(P &&... p)
|
||||
{
|
||||
void *mem = heap_alloc<T>(sizeof(T));
|
||||
return UTIL_PLACEMENT_NEW(mem) T(forward<P>(p)...);
|
||||
}
|
||||
|
||||
/* new T: default-initialized, as a new-expression without () leaves it */
|
||||
template <typename T>
|
||||
static inline T *create()
|
||||
{
|
||||
void *mem = heap_alloc<T>(sizeof(T));
|
||||
return UTIL_PLACEMENT_NEW(mem) T;
|
||||
}
|
||||
|
||||
/* delete t: a destructor that does anything runs out of line, once per
|
||||
type, as a deleting destructor does, rather than at every call */
|
||||
template <typename T>
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((noinline))
|
||||
#elif defined(_MSC_VER)
|
||||
__declspec(noinline)
|
||||
#endif
|
||||
void destroy_object(T *t)
|
||||
{
|
||||
t->~T();
|
||||
heap_free<T>(t);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline void destroy(T *t)
|
||||
{
|
||||
if (!t)
|
||||
return;
|
||||
if (UTIL_IS_TRIVIALLY_DESTRUCTIBLE(T))
|
||||
heap_free<T>(t);
|
||||
else
|
||||
destroy_object(t);
|
||||
}
|
||||
|
||||
/* new T[n] (default-initialized), and delete[] */
|
||||
template <typename T>
|
||||
static inline T *create_array(size_t n)
|
||||
{
|
||||
size_t i;
|
||||
T *a = static_cast<T *>(heap_alloc<T>(n * sizeof(T)));
|
||||
for (i = 0; i < n; i++)
|
||||
UTIL_PLACEMENT_NEW(&a[i]) T;
|
||||
return a;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline void destroy_array(T *a, size_t n)
|
||||
{
|
||||
if (a)
|
||||
{
|
||||
while (n--)
|
||||
a[n].~T();
|
||||
heap_free<T>(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Builtins of GCC, Clang and MSVC alike */
|
||||
#define UTIL_UNDERLYING_TYPE(T) __underlying_type(T)
|
||||
#define UTIL_IS_BASE_OF(B, D) __is_base_of(B, D)
|
||||
@@ -145,6 +267,7 @@ inline void operator delete(void *, Util::PlacementTag, void *) noexcept
|
||||
/* Out-of-line slow paths, and the index of the highest set bit */
|
||||
#if defined(_MSC_VER)
|
||||
#define UTIL_NOINLINE_COLD __declspec(noinline)
|
||||
#define UTIL_NOINLINE __declspec(noinline)
|
||||
static inline unsigned util_bsr32(uint32_t v)
|
||||
{
|
||||
unsigned long r;
|
||||
@@ -153,6 +276,7 @@ static inline unsigned util_bsr32(uint32_t v)
|
||||
}
|
||||
#else
|
||||
#define UTIL_NOINLINE_COLD __attribute__((noinline, cold))
|
||||
#define UTIL_NOINLINE __attribute__((noinline))
|
||||
static inline unsigned util_bsr32(uint32_t v)
|
||||
{
|
||||
return 31u - (unsigned)__builtin_clz(v);
|
||||
@@ -209,7 +333,7 @@ public:
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < count; i++)
|
||||
delete items[i];
|
||||
destroy(items[i]);
|
||||
count = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
class DebugChannelInterface;
|
||||
struct DebugChannelInterface;
|
||||
|
||||
enum CommandBufferDirtyBits
|
||||
{
|
||||
|
||||
@@ -1079,7 +1079,7 @@ bool Context::create_device(VkPhysicalDevice gpu_, VkSurfaceKHR surface, const c
|
||||
|
||||
if (device_factory)
|
||||
{
|
||||
device = device_factory->create_device(gpu, &device_info);
|
||||
device = device_factory->create_device(device_factory->user, gpu, &device_info);
|
||||
if (device == VK_NULL_HANDLE)
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -134,8 +134,8 @@ struct QueueInfo
|
||||
// libretro frontend that adds what it needs to the create info.
|
||||
struct DeviceFactory
|
||||
{
|
||||
virtual ~DeviceFactory() = default;
|
||||
virtual VkDevice create_device(VkPhysicalDevice gpu, const VkDeviceCreateInfo *info) = 0;
|
||||
VkDevice (*create_device)(void *user, VkPhysicalDevice gpu, const VkDeviceCreateInfo *info);
|
||||
void *user;
|
||||
};
|
||||
|
||||
class Context
|
||||
|
||||
@@ -39,7 +39,7 @@ DescriptorSetAllocator::DescriptorSetAllocator(Hash hash, Device *device_, const
|
||||
if (!push)
|
||||
{
|
||||
num_per_thread = device_->num_thread_indices;
|
||||
per_thread = new PerThread[num_per_thread];
|
||||
per_thread = Util::create_array<PerThread>(num_per_thread);
|
||||
}
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo info = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
|
||||
@@ -230,7 +230,7 @@ DescriptorSetAllocator::~DescriptorSetAllocator()
|
||||
if (set_layout != VK_NULL_HANDLE)
|
||||
table.vkDestroyDescriptorSetLayout(device->get_device(), set_layout, nullptr);
|
||||
clear();
|
||||
delete[] per_thread;
|
||||
Util::destroy_array(per_thread, num_per_thread);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1879,7 +1879,7 @@ void Device::init_frame_contexts(unsigned count)
|
||||
count = MAX_DEFERRED_FRAMES;
|
||||
for (unsigned i = 0; i < count; i++)
|
||||
{
|
||||
per_frame.push_back(new PerFrame(this, i));
|
||||
per_frame.push_back(Util::create<PerFrame>(this, i));
|
||||
}
|
||||
if (frame_context_index >= per_frame.size())
|
||||
frame_context_index = 0;
|
||||
@@ -4498,7 +4498,7 @@ void Device::parse_debug_channel(const PerFrame::DebugChannel &channel)
|
||||
|
||||
while (size != 0 && words[0].u32 >= 5 && words[0].u32 <= size)
|
||||
{
|
||||
channel.iface->message(channel.tag, words[1].u32,
|
||||
channel.iface->message(channel.iface->user, channel.tag, words[1].u32,
|
||||
words[2].u32, words[3].u32, words[4].u32,
|
||||
words[0].u32 - 5, &words[5]);
|
||||
size -= words[0].u32;
|
||||
|
||||
@@ -113,17 +113,18 @@ struct HandlePool
|
||||
VulkanObjectPool<DeferredInterval> deferred_intervals;
|
||||
};
|
||||
|
||||
class DebugChannelInterface
|
||||
// Where a debug channel's messages go: a function and what it is called on
|
||||
struct DebugChannelInterface
|
||||
{
|
||||
public:
|
||||
union Word
|
||||
{
|
||||
uint32_t u32;
|
||||
int32_t s32;
|
||||
float f32;
|
||||
};
|
||||
virtual void message(const char *tag, uint32_t code, uint32_t x, uint32_t y, uint32_t z,
|
||||
uint32_t word_count, const Word *words) = 0;
|
||||
void (*message)(void *user, const char *tag, uint32_t code, uint32_t x, uint32_t y, uint32_t z,
|
||||
uint32_t word_count, const Word *words);
|
||||
void *user;
|
||||
};
|
||||
|
||||
namespace Helper
|
||||
|
||||
@@ -275,7 +275,7 @@ void DeviceAllocator::init(Device *device_)
|
||||
heaps.resize(mem_props.memoryHeapCount);
|
||||
for (unsigned i = 0; i < mem_props.memoryTypeCount; i++)
|
||||
{
|
||||
allocators[i] = new Allocator(object_pool);
|
||||
allocators[i] = Util::create<Allocator>(object_pool);
|
||||
allocators[i]->set_global_allocator(this, i);
|
||||
}
|
||||
num_allocators = mem_props.memoryTypeCount;
|
||||
@@ -404,8 +404,7 @@ void DeviceAllocator::free_allocators()
|
||||
{
|
||||
for (unsigned i = 0; i < num_allocators; i++)
|
||||
{
|
||||
delete allocators[i];
|
||||
allocators[i] = nullptr;
|
||||
Util::destroy_object(allocators[i]);
|
||||
}
|
||||
num_allocators = 0;
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ private:
|
||||
Util::ObjectPool<MiniHeap> object_pool;
|
||||
Allocator *allocators[VK_MAX_MEMORY_TYPES] = {};
|
||||
unsigned num_allocators = 0;
|
||||
void free_allocators();
|
||||
UTIL_NOINLINE void free_allocators();
|
||||
Device *device = nullptr;
|
||||
const VolkDeviceTable *table = nullptr;
|
||||
VkPhysicalDeviceMemoryProperties mem_props;
|
||||
|
||||
@@ -41,7 +41,8 @@ static Util::TimelineTraceFile *timeline_trace_file;
|
||||
static CommandProcessor *frontend;
|
||||
static Device *device;
|
||||
static Context *context;
|
||||
static QueryPoolHandle begin_ts, end_ts;
|
||||
/* (held without destructors: dropped by hand while the device is up) */
|
||||
static Util::HeldHandle<Vulkan::QueryPoolResult> begin_ts, end_ts;
|
||||
|
||||
/* Set true only when init() fully succeeds (including the device meeting
|
||||
* parallel-RDP's feature requirements). The frame-driving entry points
|
||||
@@ -54,7 +55,7 @@ static bool device_supported = false;
|
||||
/* Per sync index; the frontend's index mask is 32 bits */
|
||||
#define RDP_MAX_SYNC_FRAMES 32
|
||||
static retro_vulkan_image retro_images[RDP_MAX_SYNC_FRAMES];
|
||||
static ImageHandle retro_image_handles[RDP_MAX_SYNC_FRAMES];
|
||||
static Util::HeldHandle<Vulkan::Image> retro_image_handles[RDP_MAX_SYNC_FRAMES];
|
||||
static unsigned num_retro_images;
|
||||
|
||||
/* RDRAM as the command processor imports it */
|
||||
@@ -70,7 +71,7 @@ static const struct retro_hw_render_upscaler_interface *upscaler_iface;
|
||||
static struct retro_hw_upscaler_request upscaler_request;
|
||||
static struct retro_hw_upscaler_inputs upscaler_inputs;
|
||||
/* Per sync index: motion vectors, depth, then the translucency mask */
|
||||
static ImageHandle upscaler_handles[3 * RDP_MAX_SYNC_FRAMES];
|
||||
static Util::HeldHandle<Vulkan::Image> upscaler_handles[3 * RDP_MAX_SYNC_FRAMES];
|
||||
static bool upscaler_on;
|
||||
|
||||
/* The frames released past n, as a shrinking vector did. */
|
||||
@@ -276,7 +277,7 @@ void process_commands()
|
||||
*GET_GFX_INFO(DPC_CURRENT_REG) = *GET_GFX_INFO(DPC_END_REG);
|
||||
}
|
||||
|
||||
static QueryPoolHandle refresh_begin_ts;
|
||||
static Util::HeldHandle<Vulkan::QueryPoolResult> refresh_begin_ts;
|
||||
|
||||
void profile_refresh_begin()
|
||||
{
|
||||
@@ -436,14 +437,14 @@ static bool create_processor(unsigned factor)
|
||||
}
|
||||
|
||||
/* (the old one goes first: both at once may not fit) */
|
||||
delete frontend;
|
||||
frontend = new CommandProcessor(*device, reinterpret_cast<void *>(rdram_base),
|
||||
Util::destroy(frontend);
|
||||
frontend = Util::create<CommandProcessor>(*device, reinterpret_cast<void *>(rdram_base),
|
||||
rdram_offset, 8 * 1024 * 1024, 4 * 1024 * 1024, flags);
|
||||
|
||||
if (!frontend->device_is_supported())
|
||||
{
|
||||
log_cb(RETRO_LOG_ERROR, "This device probably does not support 16-bit storage. Make sure you're using up-to-date drivers!\n");
|
||||
delete frontend;
|
||||
Util::destroy(frontend);
|
||||
frontend = nullptr;
|
||||
return false;
|
||||
}
|
||||
@@ -662,8 +663,8 @@ bool init()
|
||||
|
||||
set_num_sync_frames(num_frames);
|
||||
|
||||
delete device;
|
||||
device = new Device;
|
||||
Util::destroy(device);
|
||||
device = Util::create<Device>();
|
||||
device->set_context(*context);
|
||||
pipeline_files_load();
|
||||
device->init_frame_contexts(num_sync_frames);
|
||||
@@ -708,15 +709,15 @@ void deinit()
|
||||
set_num_sync_frames(0);
|
||||
upscaler_iface = nullptr;
|
||||
upscaler_on = false;
|
||||
delete frontend;
|
||||
Util::destroy(frontend);
|
||||
frontend = nullptr;
|
||||
if (device)
|
||||
pipeline_files_save();
|
||||
delete device;
|
||||
Util::destroy(device);
|
||||
device = nullptr;
|
||||
delete context;
|
||||
Util::destroy(context);
|
||||
context = nullptr;
|
||||
delete timeline_trace_file;
|
||||
Util::destroy(timeline_trace_file);
|
||||
timeline_trace_file = nullptr;
|
||||
}
|
||||
|
||||
@@ -925,17 +926,18 @@ namespace RDP
|
||||
{
|
||||
// vkCreateDevice through the frontend (negotiation v2), which adds the
|
||||
// extensions and features it wants, e.g. for its low-latency modes.
|
||||
struct FrontendDeviceFactory : Vulkan::DeviceFactory
|
||||
struct FrontendDeviceFactory
|
||||
{
|
||||
retro_vulkan_create_device_wrapper_t wrapper;
|
||||
void *opaque;
|
||||
|
||||
VkDevice create_device(VkPhysicalDevice gpu, const VkDeviceCreateInfo *info) override
|
||||
{
|
||||
return wrapper(gpu, opaque, info);
|
||||
}
|
||||
};
|
||||
|
||||
static VkDevice frontend_create_device(void *user, VkPhysicalDevice gpu, const VkDeviceCreateInfo *info)
|
||||
{
|
||||
auto *f = static_cast<FrontendDeviceFactory *>(user);
|
||||
return f->wrapper(gpu, f->opaque, info);
|
||||
}
|
||||
|
||||
static bool create_device(struct retro_vulkan_context *frontend_context, VkInstance instance, VkPhysicalDevice gpu,
|
||||
VkSurfaceKHR surface, PFN_vkGetInstanceProcAddr get_instance_proc_addr,
|
||||
const char **required_device_extensions, unsigned num_required_device_extensions,
|
||||
@@ -944,15 +946,15 @@ static bool create_device(struct retro_vulkan_context *frontend_context, VkInsta
|
||||
if (!Vulkan::Context::init_loader(get_instance_proc_addr))
|
||||
return false;
|
||||
|
||||
delete ::RDP::context;
|
||||
::RDP::context = new Vulkan::Context;
|
||||
Util::destroy(::RDP::context);
|
||||
::RDP::context = Util::create<Vulkan::Context>();
|
||||
|
||||
::Vulkan::Context::SystemHandles handles;
|
||||
|
||||
if (const char *env = getenv("PARALLEL_RDP_TIMELINE_TRACE"))
|
||||
{
|
||||
delete ::RDP::timeline_trace_file;
|
||||
::RDP::timeline_trace_file = new Util::TimelineTraceFile(env);
|
||||
Util::destroy(::RDP::timeline_trace_file);
|
||||
::RDP::timeline_trace_file = Util::create<Util::TimelineTraceFile>(env);
|
||||
handles.timeline_trace_file = ::RDP::timeline_trace_file;
|
||||
}
|
||||
|
||||
@@ -965,7 +967,7 @@ static bool create_device(struct retro_vulkan_context *frontend_context, VkInsta
|
||||
::RDP::context->set_device_factory(nullptr);
|
||||
if (!ok)
|
||||
{
|
||||
delete ::RDP::context;
|
||||
Util::destroy(::RDP::context);
|
||||
::RDP::context = nullptr;
|
||||
return false;
|
||||
}
|
||||
@@ -999,9 +1001,12 @@ bool parallel_create_device2(struct retro_vulkan_context *frontend_context, VkIn
|
||||
retro_vulkan_create_device_wrapper_t create_device_wrapper, void *opaque)
|
||||
{
|
||||
static const VkPhysicalDeviceFeatures no_features = {};
|
||||
::RDP::FrontendDeviceFactory factory;
|
||||
factory.wrapper = create_device_wrapper;
|
||||
factory.opaque = opaque;
|
||||
::RDP::FrontendDeviceFactory frontend;
|
||||
Vulkan::DeviceFactory factory;
|
||||
frontend.wrapper = create_device_wrapper;
|
||||
frontend.opaque = opaque;
|
||||
factory.create_device = ::RDP::frontend_create_device;
|
||||
factory.user = &frontend;
|
||||
return ::RDP::create_device(frontend_context, instance, gpu, surface, get_instance_proc_addr,
|
||||
nullptr, 0, &no_features, &factory);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""parallel-RDP and its frontend glue must not use the C++ runtime.
|
||||
|
||||
They allocate with malloc and free and construct in place (Util::create,
|
||||
Util::destroy), call through structs of function pointers instead of
|
||||
virtual interfaces, and hold their static handles without destructors.
|
||||
So none of their objects references operator new or delete, the ABI
|
||||
support library (__cxa_*, the type_info vtables), a personality routine
|
||||
or the standard library, and none defines a vtable, type_info or type
|
||||
name. Any of those coming back, in a header used by them as much as in
|
||||
their own code, fails this, in the objects of a finished build.
|
||||
|
||||
check_cxx_runtime.py [dir ...] (run after the build, from the repo root)
|
||||
"""
|
||||
import os, re, subprocess, sys
|
||||
|
||||
DIRS = sys.argv[1:] or ["mupen64plus-video-paraLLEl"]
|
||||
RUNTIME = re.compile(r"^(_Znw|_Zna|_Zdl|_Zda|__cxa_|_ZTI|_ZTS|_ZTV|__gxx_personality|_ZSt|_ZNSt)")
|
||||
|
||||
|
||||
def main():
|
||||
objects = 0
|
||||
found = []
|
||||
for d in DIRS:
|
||||
for root, _, files in os.walk(d):
|
||||
for f in sorted(files):
|
||||
if not f.endswith(".o"):
|
||||
continue
|
||||
path = os.path.join(root, f)
|
||||
objects += 1
|
||||
out = subprocess.run(["nm", path], stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL, text=True).stdout
|
||||
names = sorted(set(line.split()[-1] for line in out.splitlines()
|
||||
if line.split() and RUNTIME.match(line.split()[-1])))
|
||||
if names:
|
||||
found.append("%s: %s" % (path, " ".join(names)))
|
||||
if not objects:
|
||||
print("check_cxx_runtime: no objects; build first")
|
||||
return 1
|
||||
for line in found:
|
||||
print("C++ runtime in %s" % line)
|
||||
print("check_cxx_runtime: %s" % ("ok (%d objects)" % objects if not found
|
||||
else "%d objects use it" % len(found)))
|
||||
return 1 if found else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user