Fix particle sizes, and add tools for comparing renderers
Particles: Direct3D 9's size limit evidently isn't the documented 64 pixels; unclamped (up to 2048) they match the client's renderer. A compare view shows the left half's scene over the right half, both renderers side by side; %TEMP%\t5de-renderer-view.txt switches views and %TEMP%\t5de-renderer-shaders\fixed_function.hlsl replaces the shaders while the client runs. The renderer process runs from a copy, so the program can be replaced meanwhile. The adapter recognizes only the textures and buffers it made (others reach the client renderer unchanged) and logs the others and render targets. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
1a45bf9a86
commit
299cd8c312
11 files changed
+239
-28
No files matched your search
@@ -73,6 +73,12 @@ void Engine::setViewSpace(const imvu::Transform& view)
|
||||
void Engine::setTextures(imvu::PiRenderTexture* texture0, imvu::PiRenderTexture* texture1)
|
||||
{
|
||||
inner_->setTextures(inner(texture0), inner(texture1));
|
||||
for (imvu::PiRenderTexture* texture : {texture0, texture1}) {
|
||||
if (texture != nullptr && id(texture) == 0 && foreignLogged_ < 20) {
|
||||
++foreignLogged_;
|
||||
log::info("engine: frame {}: a texture the client renderer made ({})", frames_, static_cast<void*>(texture));
|
||||
}
|
||||
}
|
||||
recorder_->add(Op::SetTextures, protocol::SetTextures{id(texture0), id(texture1)});
|
||||
}
|
||||
|
||||
@@ -219,17 +225,23 @@ imvu::PiInternalVertexBuffer* Engine::newDynamicVertexBuffer(std::uint32_t eleme
|
||||
|
||||
imvu::SharedPtr<imvu::PiPixmapRenderTarget> Engine::newPixmapRenderTarget(imvu::Vec2i size)
|
||||
{
|
||||
log::info("engine: frame {}: a {}x{} pixmap render target", frames_, size.x, size.y);
|
||||
return inner_->newPixmapRenderTarget(size);
|
||||
}
|
||||
|
||||
imvu::SharedPtr<imvu::PiTextureRenderTarget> Engine::newTextureRenderTarget(imvu::Vec2i size)
|
||||
{
|
||||
log::info("engine: frame {}: a {}x{} texture render target", frames_, size.x, size.y);
|
||||
return inner_->newTextureRenderTarget(size);
|
||||
}
|
||||
|
||||
void Engine::beginRendering(imvu::PiRenderTarget* target)
|
||||
{
|
||||
inner_->beginRendering(target);
|
||||
if (target != nullptr && foreignLogged_ < 20) {
|
||||
++foreignLogged_;
|
||||
log::info("engine: frame {}: drawing into a render target ({})", frames_, static_cast<void*>(target));
|
||||
}
|
||||
recorder_->add(Op::BeginFrame, protocol::BeginFrame{target != nullptr});
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,8 @@ private:
|
||||
imvu::SharedPtr<imvu::PiRenderEngine> inner_;
|
||||
std::shared_ptr<Recorder> recorder_;
|
||||
std::uint64_t frames_ = 0;
|
||||
int foreignLogged_ = 0; // diagnostics: textures the client renderer made, render targets
|
||||
|
||||
};
|
||||
|
||||
} // namespace t5de
|
||||
+6
-2
@@ -33,13 +33,17 @@ imvu::SharedPtr<imvu::PiRenderEngine> create_imvu_engine(imvu::SharedPtr<imvu::I
|
||||
return create(std::move(window), imvu::RendererType::Direct3D);
|
||||
}
|
||||
|
||||
// What the renderer process draws: T5DE_RENDERER_VIEW=off, split (the default: the right half) or full.
|
||||
// What the renderer process draws: T5DE_RENDERER_VIEW=off, split (the default: the right half), full or compare.
|
||||
// (The renderer process can also be switched while it runs: renderer/main.cpp.)
|
||||
protocol::View view()
|
||||
{
|
||||
wchar_t value[16] = {};
|
||||
GetEnvironmentVariableW(L"T5DE_RENDERER_VIEW", value, 16);
|
||||
const std::wstring_view setting = value;
|
||||
return setting == L"off" ? protocol::View::Off : setting == L"full" ? protocol::View::Full : protocol::View::Split;
|
||||
return setting == L"off" ? protocol::View::Off
|
||||
: setting == L"full" ? protocol::View::Full
|
||||
: setting == L"compare" ? protocol::View::Compare
|
||||
: protocol::View::Split;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -29,6 +29,31 @@ std::filesystem::path module_folder()
|
||||
|
||||
std::string narrow(const std::wstring& text) { return std::filesystem::path(text).string(); }
|
||||
|
||||
// The renderer process runs from a copy in %TEMP%\t5de-renderer, one per version of the program (by size and
|
||||
// modification time), so the program in the client's folder can be replaced (updated) while the client runs: the
|
||||
// next engine runs the new one. Copies no longer running are removed. Falls back to the program itself.
|
||||
std::filesystem::path running_copy(const std::filesystem::path& exe)
|
||||
{
|
||||
std::error_code error;
|
||||
const auto size = std::filesystem::file_size(exe, error);
|
||||
const auto modified = std::filesystem::last_write_time(exe, error);
|
||||
if (error) {
|
||||
return exe;
|
||||
}
|
||||
const auto folder = std::filesystem::temp_directory_path(error) / L"t5de-renderer";
|
||||
std::filesystem::create_directories(folder, error);
|
||||
const auto copy = folder / std::format(L"t5de-renderer-{}-{}.exe", size, modified.time_since_epoch().count());
|
||||
if (!std::filesystem::exists(copy, error)) {
|
||||
for (const auto& old : std::filesystem::directory_iterator(folder, error)) {
|
||||
std::filesystem::remove(old.path(), error); // fails, harmlessly, for copies still running
|
||||
}
|
||||
if (!CopyFileW(exe.c_str(), copy.c_str(), FALSE)) {
|
||||
return exe;
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<RendererProcess> RendererProcess::start(HWND window, protocol::View view)
|
||||
@@ -64,7 +89,7 @@ std::unique_ptr<RendererProcess> RendererProcess::start(HWND window, protocol::V
|
||||
// The renderer's window goes on top of the client's 3D view: keep the client's drawing out of it.
|
||||
SetWindowLongW(window, GWL_STYLE, GetWindowLongW(window, GWL_STYLE) | WS_CLIPCHILDREN);
|
||||
|
||||
const auto exe = module_folder() / L"t5de-renderer.exe";
|
||||
const auto exe = running_copy(module_folder() / L"t5de-renderer.exe");
|
||||
std::wstring command = std::format(L"\"{}\" --channel {} --parent {}", exe.wstring(), name, GetCurrentProcessId());
|
||||
STARTUPINFOW startup{.cb = sizeof(STARTUPINFOW)};
|
||||
PROCESS_INFORMATION process{};
|
||||
|
||||
@@ -1,14 +1,56 @@
|
||||
#include "resources.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace t5de {
|
||||
|
||||
namespace {
|
||||
|
||||
// Our live resources, by address.
|
||||
struct Registry {
|
||||
std::mutex mutex;
|
||||
std::unordered_set<const void*> objects;
|
||||
};
|
||||
|
||||
Registry& registry()
|
||||
{
|
||||
static Registry instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void add(const void* object)
|
||||
{
|
||||
const std::scoped_lock lock(registry().mutex);
|
||||
registry().objects.insert(object);
|
||||
}
|
||||
|
||||
void remove(const void* object)
|
||||
{
|
||||
const std::scoped_lock lock(registry().mutex);
|
||||
registry().objects.erase(object);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ours(const void* object)
|
||||
{
|
||||
if (object == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const std::scoped_lock lock(registry().mutex);
|
||||
return registry().objects.contains(object);
|
||||
}
|
||||
|
||||
Texture::Texture(imvu::PiRenderTexture* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id)
|
||||
: inner_(inner), recorder_(std::move(recorder)), id_(id)
|
||||
{
|
||||
add(static_cast<imvu::PiRenderTexture*>(this));
|
||||
}
|
||||
|
||||
Texture::~Texture()
|
||||
{
|
||||
remove(static_cast<imvu::PiRenderTexture*>(this));
|
||||
delete inner_; // through its virtual destructor: IMVU's code frees it
|
||||
recorder_->destroyTexture(id_);
|
||||
}
|
||||
@@ -32,10 +74,12 @@ imvu::Pixmap* Texture::read(imvu::Pixmap* result) const { return inner_->read(re
|
||||
IndexBuffer::IndexBuffer(imvu::PiInternalIndexBuffer* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id)
|
||||
: inner_(inner), recorder_(std::move(recorder)), id_(id)
|
||||
{
|
||||
add(static_cast<imvu::PiInternalIndexBuffer*>(this));
|
||||
}
|
||||
|
||||
IndexBuffer::~IndexBuffer()
|
||||
{
|
||||
remove(static_cast<imvu::PiInternalIndexBuffer*>(this));
|
||||
delete inner_;
|
||||
recorder_->destroyBuffer(id_);
|
||||
}
|
||||
@@ -43,10 +87,12 @@ IndexBuffer::~IndexBuffer()
|
||||
VertexBuffer::VertexBuffer(imvu::PiInternalVertexBuffer* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id)
|
||||
: inner_(inner), recorder_(std::move(recorder)), id_(id)
|
||||
{
|
||||
add(static_cast<imvu::PiInternalVertexBuffer*>(this));
|
||||
}
|
||||
|
||||
VertexBuffer::~VertexBuffer()
|
||||
{
|
||||
remove(static_cast<imvu::PiInternalVertexBuffer*>(this));
|
||||
delete inner_;
|
||||
recorder_->destroyBuffer(id_);
|
||||
}
|
||||
|
||||
+11
-7
@@ -62,30 +62,34 @@ private:
|
||||
std::uint32_t id_;
|
||||
};
|
||||
|
||||
// The client renderer's object and our id for something the engine made; null and 0 for null.
|
||||
// Whether an object is one of ours (live Texture, IndexBuffer or VertexBuffer): the client can also hand the engine
|
||||
// objects the client renderer made itself (render targets' textures), which must reach it unchanged.
|
||||
bool ours(const void* object);
|
||||
|
||||
// The client renderer's object, and our id, for what the client passes in: ours unwrapped, others unchanged (id 0).
|
||||
inline imvu::PiRenderTexture* inner(imvu::PiRenderTexture* texture)
|
||||
{
|
||||
return texture != nullptr ? static_cast<Texture*>(texture)->inner() : nullptr;
|
||||
return ours(texture) ? static_cast<Texture*>(texture)->inner() : texture;
|
||||
}
|
||||
inline std::uint32_t id(imvu::PiRenderTexture* texture)
|
||||
{
|
||||
return texture != nullptr ? static_cast<Texture*>(texture)->id() : 0;
|
||||
return ours(texture) ? static_cast<Texture*>(texture)->id() : 0;
|
||||
}
|
||||
inline imvu::PiInternalIndexBuffer* inner(imvu::PiInternalIndexBuffer* buffer)
|
||||
{
|
||||
return buffer != nullptr ? static_cast<IndexBuffer*>(buffer)->inner() : nullptr;
|
||||
return ours(buffer) ? static_cast<IndexBuffer*>(buffer)->inner() : buffer;
|
||||
}
|
||||
inline std::uint32_t id(imvu::PiInternalIndexBuffer* buffer)
|
||||
{
|
||||
return buffer != nullptr ? static_cast<IndexBuffer*>(buffer)->id() : 0;
|
||||
return ours(buffer) ? static_cast<IndexBuffer*>(buffer)->id() : 0;
|
||||
}
|
||||
inline imvu::PiInternalVertexBuffer* inner(imvu::PiInternalVertexBuffer* buffer)
|
||||
{
|
||||
return buffer != nullptr ? static_cast<VertexBuffer*>(buffer)->inner() : nullptr;
|
||||
return ours(buffer) ? static_cast<VertexBuffer*>(buffer)->inner() : buffer;
|
||||
}
|
||||
inline std::uint32_t id(imvu::PiInternalVertexBuffer* buffer)
|
||||
{
|
||||
return buffer != nullptr ? static_cast<VertexBuffer*>(buffer)->id() : 0;
|
||||
return ours(buffer) ? static_cast<VertexBuffer*>(buffer)->id() : 0;
|
||||
}
|
||||
|
||||
} // namespace t5de
|
||||
Reference in new issue
Block a user