Add a pass-through renderer plugin
t5deRenderEngine.dll implements IMVU Classic's renderer plugin interface by wrapping IMVU's own OpenGL renderer, and logs what each frame calls, to prove our declarations of the interface. Built with CMake presets for 32-bit MSVC; docs/interface.md has the findings. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
dc643d271b
16 files changed
+1037
No files matched your search
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
// IMVU's renderer interface, PiRenderEngine (SceneWindow.dll), declared to match its vtable: the order of the methods
|
||||
// and their parameter types are what matter. The names are IMVU's, from SceneWindow.dll's exported RenderEngineDx
|
||||
// methods; parameter names are ours, and guesses until the calls are traced. Types used only through pointers or
|
||||
// references are left incomplete until we need their layout. See docs/interface.md.
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "t5de/imvu/shared_ptr.hpp"
|
||||
|
||||
namespace t5de::imvu {
|
||||
|
||||
template <class T>
|
||||
struct Vec2 {
|
||||
T x;
|
||||
T y;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct Vec3 {
|
||||
T x;
|
||||
T y;
|
||||
T z;
|
||||
};
|
||||
|
||||
using Vec2i = Vec2<int>;
|
||||
using Vec2f = Vec2<float>;
|
||||
using Vec3f = Vec3<float>;
|
||||
|
||||
struct Mat4f;
|
||||
struct Transform;
|
||||
struct LightProperties;
|
||||
struct TextureTransform;
|
||||
struct PointSprite;
|
||||
class Pixmap;
|
||||
class INativeWindow;
|
||||
class PiRenderTexture;
|
||||
class PiInternalIndexBuffer;
|
||||
class PiInternalVertexBuffer;
|
||||
class PiRenderTarget;
|
||||
class PiPixmapRenderTarget;
|
||||
class PiTextureRenderTarget;
|
||||
template <class T>
|
||||
class VertexAttributeArray;
|
||||
|
||||
// std::vector<PointSprite> as Visual C++ 2010 lays it out.
|
||||
struct PointSpriteVector;
|
||||
|
||||
// Values not yet known.
|
||||
enum class RenderBlendMode : int {};
|
||||
|
||||
class PiRenderEngine {
|
||||
public:
|
||||
PiRenderEngine() = default;
|
||||
PiRenderEngine(const PiRenderEngine&) = delete;
|
||||
PiRenderEngine& operator=(const PiRenderEngine&) = delete;
|
||||
|
||||
virtual ~PiRenderEngine() = default; // slot 0
|
||||
|
||||
virtual void resizeFrameBuffer(Vec2i size) = 0;
|
||||
virtual void enable2dFrameBuffer() = 0;
|
||||
virtual void clear(std::uint32_t flags, std::uint32_t color) = 0;
|
||||
virtual void setProjection(Vec2i viewport, const Mat4f& projection) = 0;
|
||||
virtual void setModelSpace(const Transform& model) = 0; // slot 5
|
||||
virtual void setViewSpace(const Transform& view) = 0;
|
||||
virtual void setTextures(PiRenderTexture* texture0, PiRenderTexture* texture1) = 0;
|
||||
virtual void setDepthTestingOn(bool on) = 0;
|
||||
virtual void setDepthWritingOn(bool on) = 0;
|
||||
virtual void setBlendMode(RenderBlendMode mode) = 0; // slot 10
|
||||
virtual void setAlphaTestingOn(bool on) = 0;
|
||||
virtual void setAlphaTestValue(float value) = 0;
|
||||
virtual void setBlendingOn(bool on) = 0;
|
||||
virtual void setBackFaceCullingOn(bool on) = 0;
|
||||
virtual void setNormalInvertingOn(bool on) = 0; // slot 15
|
||||
virtual void setLightingOn(bool on) = 0;
|
||||
virtual void setLightProperties(std::uint32_t index, const LightProperties& light) = 0;
|
||||
virtual void setFogOn(bool on) = 0;
|
||||
virtual void setFogAttributes(float start, float end, std::uint32_t color) = 0;
|
||||
virtual void setTextureTransformMatrixIdentity(std::uint32_t stage) = 0; // slot 20
|
||||
virtual void setTextureTransformMatrix(std::uint32_t stage, const TextureTransform& transform) = 0;
|
||||
virtual void setEnvironmentAmbient(std::uint32_t color) = 0;
|
||||
virtual PiRenderTexture* newRenderTexture(Pixmap* pixmap, std::uint32_t flags) = 0;
|
||||
virtual PiInternalIndexBuffer* newIndexBuffer(std::uint32_t count, const std::uint16_t* indices) = 0;
|
||||
virtual PiInternalVertexBuffer* newStaticVertexBuffer(const void* data, std::uint32_t a, std::uint32_t b,
|
||||
std::uint32_t c) = 0; // slot 25
|
||||
virtual PiInternalVertexBuffer* newDynamicVertexBuffer(std::uint32_t a, std::uint32_t b) = 0;
|
||||
virtual SharedPtr<PiPixmapRenderTarget> newPixmapRenderTarget(Vec2i size) = 0;
|
||||
virtual SharedPtr<PiTextureRenderTarget> newTextureRenderTarget(Vec2i size) = 0;
|
||||
virtual void beginRendering(PiRenderTarget* target) = 0;
|
||||
virtual void endRendering() = 0; // slot 30
|
||||
virtual void drawTriangles(PiInternalIndexBuffer* indices, PiInternalVertexBuffer* positions,
|
||||
PiInternalVertexBuffer* normals, PiInternalVertexBuffer* texCoords0,
|
||||
PiInternalVertexBuffer* texCoords1) = 0;
|
||||
virtual void drawLine(Vec3f from, Vec3f to, std::uint32_t color) = 0;
|
||||
virtual void drawPointSprites(const PointSpriteVector& sprites) = 0;
|
||||
virtual void drawTrianglesFromMemory(std::uint32_t vertexCount, std::uint32_t indexCount,
|
||||
const std::uint16_t* indices, const VertexAttributeArray<Vec3f>& positions,
|
||||
const VertexAttributeArray<Vec3f>& normals,
|
||||
const VertexAttributeArray<Vec2f>& texCoords,
|
||||
const VertexAttributeArray<std::uint32_t>& colors) = 0; // slot 34
|
||||
|
||||
private:
|
||||
// PiRenderEngine's own fields, after the vtable pointer: a magic number IMVU's destructor checks, and a size
|
||||
// counter it reports from.
|
||||
std::uint32_t magic_ = 0x3b913159;
|
||||
std::uint32_t size_ = 0;
|
||||
};
|
||||
|
||||
static_assert(sizeof(PiRenderEngine) == 12, "vtable pointer, magic, size");
|
||||
|
||||
// What SceneWindow.dll's createDLLRenderer looks up in a renderer plugin.
|
||||
using IsRenderEngineSupported = int(__cdecl*)();
|
||||
using CreateRenderEngine = SharedPtr<PiRenderEngine>(__cdecl*)(SharedPtr<INativeWindow> window);
|
||||
|
||||
} // namespace t5de::imvu
|
||||
@@ -0,0 +1,129 @@
|
||||
#pragma once
|
||||
// boost::shared_ptr as IMVU's DLLs lay it out (Boost 1.61, Visual C++ 2010, 32-bit): the object pointer, then a
|
||||
// pointer to the control block. Shared pointers cross the plugin boundary in both directions, so ours must be
|
||||
// interchangeable with theirs: same layout, same control block vtable, same atomic counts.
|
||||
|
||||
#include <intrin.h>
|
||||
|
||||
#include <memory>
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
|
||||
namespace t5de::imvu {
|
||||
|
||||
// boost::detail::sp_counted_base: its virtual methods, in this order, then the use and weak counts.
|
||||
class CountedBase {
|
||||
public:
|
||||
CountedBase() = default;
|
||||
CountedBase(const CountedBase&) = delete;
|
||||
CountedBase& operator=(const CountedBase&) = delete;
|
||||
|
||||
virtual ~CountedBase() = default;
|
||||
// Destroys the object; called when the use count reaches 0.
|
||||
virtual void dispose() noexcept = 0;
|
||||
// Destroys the control block; called when the weak count reaches 0.
|
||||
virtual void destroy() noexcept { delete this; }
|
||||
virtual void* get_deleter(const std::type_info&) noexcept { return nullptr; }
|
||||
virtual void* get_untyped_deleter() noexcept { return nullptr; }
|
||||
|
||||
void add_ref() noexcept { _InterlockedIncrement(&use_); }
|
||||
|
||||
void release() noexcept
|
||||
{
|
||||
if (_InterlockedDecrement(&use_) == 0) {
|
||||
dispose();
|
||||
weak_release();
|
||||
}
|
||||
}
|
||||
|
||||
void weak_release() noexcept
|
||||
{
|
||||
if (_InterlockedDecrement(&weak_) == 0) {
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] long use_count() const noexcept { return use_; }
|
||||
|
||||
private:
|
||||
long use_ = 1;
|
||||
long weak_ = 1;
|
||||
};
|
||||
|
||||
static_assert(sizeof(CountedBase) == 12, "vtable pointer, use count, weak count");
|
||||
|
||||
// A control block owning a heap object, deleted with delete (boost's sp_counted_impl_p).
|
||||
template <class T>
|
||||
class CountedPointer final : public CountedBase {
|
||||
public:
|
||||
explicit CountedPointer(T* object) noexcept : object_(object) {}
|
||||
void dispose() noexcept override { delete object_; }
|
||||
|
||||
private:
|
||||
T* object_;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class SharedPtr {
|
||||
public:
|
||||
SharedPtr() noexcept = default;
|
||||
|
||||
explicit SharedPtr(std::unique_ptr<T> object) : object_(object.get())
|
||||
{
|
||||
if (object_ != nullptr) {
|
||||
counts_ = new CountedPointer<T>(object_);
|
||||
object.release();
|
||||
}
|
||||
}
|
||||
|
||||
// Shares ownership of a derived object (the counts stay with the object's own control block).
|
||||
template <class U>
|
||||
requires std::is_convertible_v<U*, T*>
|
||||
SharedPtr(SharedPtr<U> other) noexcept // NOLINT(google-explicit-constructor): like boost's
|
||||
: object_(std::exchange(other.object_, nullptr)), counts_(std::exchange(other.counts_, nullptr))
|
||||
{
|
||||
}
|
||||
|
||||
SharedPtr(const SharedPtr& other) noexcept : object_(other.object_), counts_(other.counts_)
|
||||
{
|
||||
if (counts_ != nullptr) {
|
||||
counts_->add_ref();
|
||||
}
|
||||
}
|
||||
|
||||
SharedPtr(SharedPtr&& other) noexcept
|
||||
: object_(std::exchange(other.object_, nullptr)), counts_(std::exchange(other.counts_, nullptr))
|
||||
{
|
||||
}
|
||||
|
||||
SharedPtr& operator=(SharedPtr other) noexcept
|
||||
{
|
||||
std::swap(object_, other.object_);
|
||||
std::swap(counts_, other.counts_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~SharedPtr()
|
||||
{
|
||||
if (counts_ != nullptr) {
|
||||
counts_->release();
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] T* get() const noexcept { return object_; }
|
||||
T* operator->() const noexcept { return object_; }
|
||||
T& operator*() const noexcept { return *object_; }
|
||||
explicit operator bool() const noexcept { return object_ != nullptr; }
|
||||
[[nodiscard]] long use_count() const noexcept { return counts_ != nullptr ? counts_->use_count() : 0; }
|
||||
|
||||
private:
|
||||
template <class U>
|
||||
friend class SharedPtr;
|
||||
|
||||
T* object_ = nullptr;
|
||||
CountedBase* counts_ = nullptr;
|
||||
};
|
||||
|
||||
static_assert(sizeof(SharedPtr<int>) == 8, "object pointer, control block pointer");
|
||||
|
||||
} // namespace t5de::imvu
|
||||
Reference in new issue
Block a user