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)
|
void Engine::setTextures(imvu::PiRenderTexture* texture0, imvu::PiRenderTexture* texture1)
|
||||||
{
|
{
|
||||||
inner_->setTextures(inner(texture0), inner(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)});
|
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)
|
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);
|
return inner_->newPixmapRenderTarget(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
imvu::SharedPtr<imvu::PiTextureRenderTarget> Engine::newTextureRenderTarget(imvu::Vec2i 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);
|
return inner_->newTextureRenderTarget(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::beginRendering(imvu::PiRenderTarget* target)
|
void Engine::beginRendering(imvu::PiRenderTarget* target)
|
||||||
{
|
{
|
||||||
inner_->beginRendering(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});
|
recorder_->add(Op::BeginFrame, protocol::BeginFrame{target != nullptr});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ private:
|
|||||||
imvu::SharedPtr<imvu::PiRenderEngine> inner_;
|
imvu::SharedPtr<imvu::PiRenderEngine> inner_;
|
||||||
std::shared_ptr<Recorder> recorder_;
|
std::shared_ptr<Recorder> recorder_;
|
||||||
std::uint64_t frames_ = 0;
|
std::uint64_t frames_ = 0;
|
||||||
|
int foreignLogged_ = 0; // diagnostics: textures the client renderer made, render targets
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace t5de
|
} // 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);
|
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()
|
protocol::View view()
|
||||||
{
|
{
|
||||||
wchar_t value[16] = {};
|
wchar_t value[16] = {};
|
||||||
GetEnvironmentVariableW(L"T5DE_RENDERER_VIEW", value, 16);
|
GetEnvironmentVariableW(L"T5DE_RENDERER_VIEW", value, 16);
|
||||||
const std::wstring_view setting = value;
|
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
|
} // namespace
|
||||||
|
|||||||
@@ -29,6 +29,31 @@ std::filesystem::path module_folder()
|
|||||||
|
|
||||||
std::string narrow(const std::wstring& text) { return std::filesystem::path(text).string(); }
|
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
|
} // namespace
|
||||||
|
|
||||||
std::unique_ptr<RendererProcess> RendererProcess::start(HWND window, protocol::View view)
|
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.
|
// 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);
|
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());
|
std::wstring command = std::format(L"\"{}\" --channel {} --parent {}", exe.wstring(), name, GetCurrentProcessId());
|
||||||
STARTUPINFOW startup{.cb = sizeof(STARTUPINFOW)};
|
STARTUPINFOW startup{.cb = sizeof(STARTUPINFOW)};
|
||||||
PROCESS_INFORMATION process{};
|
PROCESS_INFORMATION process{};
|
||||||
|
|||||||
@@ -1,14 +1,56 @@
|
|||||||
#include "resources.hpp"
|
#include "resources.hpp"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace t5de {
|
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)
|
Texture::Texture(imvu::PiRenderTexture* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id)
|
||||||
: inner_(inner), recorder_(std::move(recorder)), id_(id)
|
: inner_(inner), recorder_(std::move(recorder)), id_(id)
|
||||||
{
|
{
|
||||||
|
add(static_cast<imvu::PiRenderTexture*>(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
Texture::~Texture()
|
Texture::~Texture()
|
||||||
{
|
{
|
||||||
|
remove(static_cast<imvu::PiRenderTexture*>(this));
|
||||||
delete inner_; // through its virtual destructor: IMVU's code frees it
|
delete inner_; // through its virtual destructor: IMVU's code frees it
|
||||||
recorder_->destroyTexture(id_);
|
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)
|
IndexBuffer::IndexBuffer(imvu::PiInternalIndexBuffer* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id)
|
||||||
: inner_(inner), recorder_(std::move(recorder)), id_(id)
|
: inner_(inner), recorder_(std::move(recorder)), id_(id)
|
||||||
{
|
{
|
||||||
|
add(static_cast<imvu::PiInternalIndexBuffer*>(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
IndexBuffer::~IndexBuffer()
|
IndexBuffer::~IndexBuffer()
|
||||||
{
|
{
|
||||||
|
remove(static_cast<imvu::PiInternalIndexBuffer*>(this));
|
||||||
delete inner_;
|
delete inner_;
|
||||||
recorder_->destroyBuffer(id_);
|
recorder_->destroyBuffer(id_);
|
||||||
}
|
}
|
||||||
@@ -43,10 +87,12 @@ IndexBuffer::~IndexBuffer()
|
|||||||
VertexBuffer::VertexBuffer(imvu::PiInternalVertexBuffer* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id)
|
VertexBuffer::VertexBuffer(imvu::PiInternalVertexBuffer* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id)
|
||||||
: inner_(inner), recorder_(std::move(recorder)), id_(id)
|
: inner_(inner), recorder_(std::move(recorder)), id_(id)
|
||||||
{
|
{
|
||||||
|
add(static_cast<imvu::PiInternalVertexBuffer*>(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
VertexBuffer::~VertexBuffer()
|
VertexBuffer::~VertexBuffer()
|
||||||
{
|
{
|
||||||
|
remove(static_cast<imvu::PiInternalVertexBuffer*>(this));
|
||||||
delete inner_;
|
delete inner_;
|
||||||
recorder_->destroyBuffer(id_);
|
recorder_->destroyBuffer(id_);
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-7
@@ -62,30 +62,34 @@ private:
|
|||||||
std::uint32_t id_;
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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
|
} // namespace t5de
|
||||||
@@ -11,9 +11,10 @@ inline constexpr std::uint32_t version = 2;
|
|||||||
|
|
||||||
// Which part of the client's 3D view the renderer process draws (the client's own renderer draws all of it, under).
|
// Which part of the client's 3D view the renderer process draws (the client's own renderer draws all of it, under).
|
||||||
enum class View : std::uint32_t {
|
enum class View : std::uint32_t {
|
||||||
Off, // none
|
Off, // none
|
||||||
Split, // the right half, to compare with the client's renderer on the left
|
Split, // the right half, to compare with the client's renderer on the left
|
||||||
Full,
|
Full,
|
||||||
|
Compare, // over the right half, the left half's scene: both renderers side by side, showing the same
|
||||||
};
|
};
|
||||||
|
|
||||||
// A shared memory section the adapter creates for each engine, named by the renderer process's `--channel <name>`
|
// A shared memory section the adapter creates for each engine, named by the renderer process's `--channel <name>`
|
||||||
|
|||||||
+95
-13
@@ -84,15 +84,90 @@ HWND create_child_window(HWND parent)
|
|||||||
0, 0, 1, 1, parent, nullptr, windowClass.hInstance, nullptr);
|
0, 0, 1, 1, parent, nullptr, windowClass.hInstance, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Where the child window goes in the client's 3D view.
|
// The view, as the adapter set it, or as %TEMP%\t5de-renderer-view.txt says while the client runs (off, split, full
|
||||||
RECT placement(HWND parent, View view)
|
// or compare): for comparing the renderers without restarting the client. Read every second or so.
|
||||||
|
View current_view(View adapter)
|
||||||
{
|
{
|
||||||
RECT client{};
|
static View view = adapter;
|
||||||
GetClientRect(parent, &client);
|
static ULONGLONG checked = 0;
|
||||||
if (view == View::Split) {
|
if (GetTickCount64() - checked < 1000) {
|
||||||
client.left = client.right / 2;
|
return view;
|
||||||
}
|
}
|
||||||
return client;
|
checked = GetTickCount64();
|
||||||
|
view = adapter;
|
||||||
|
wchar_t temp[MAX_PATH + 1];
|
||||||
|
const DWORD length = GetTempPathW(MAX_PATH + 1, temp);
|
||||||
|
if (length == 0 || length > MAX_PATH) {
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
const HANDLE file = CreateFileW((std::filesystem::path(temp) / L"t5de-renderer-view.txt").c_str(), GENERIC_READ,
|
||||||
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
|
||||||
|
FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||||
|
if (file == INVALID_HANDLE_VALUE) {
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
char text[16] = {};
|
||||||
|
DWORD read = 0;
|
||||||
|
ReadFile(file, text, sizeof(text) - 1, &read, nullptr);
|
||||||
|
CloseHandle(file);
|
||||||
|
const std::string_view setting(text, std::strcspn(text, "\r\n "));
|
||||||
|
view = setting == "off" ? View::Off
|
||||||
|
: setting == "split" ? View::Split
|
||||||
|
: setting == "full" ? View::Full
|
||||||
|
: setting == "compare" ? View::Compare
|
||||||
|
: adapter;
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shader changes to try while the client runs: %TEMP%\t5de-renderer-shaders\fixed_function.hlsl, if there is
|
||||||
|
// one, replaces the built-in shaders, and is read again when it changes (checked every second or so).
|
||||||
|
void reload_shaders(t5de::Scene& scene)
|
||||||
|
{
|
||||||
|
static ULONGLONG checked = 0;
|
||||||
|
static std::filesystem::file_time_type loaded{};
|
||||||
|
if (GetTickCount64() - checked < 1000) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
checked = GetTickCount64();
|
||||||
|
std::error_code error;
|
||||||
|
const auto path = std::filesystem::temp_directory_path(error) / L"t5de-renderer-shaders" / L"fixed_function.hlsl";
|
||||||
|
const auto modified = std::filesystem::last_write_time(path, error);
|
||||||
|
if (error || modified == loaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
|
||||||
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||||
|
if (file == INVALID_HANDLE_VALUE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string source(static_cast<std::size_t>(std::filesystem::file_size(path, error)), ' ');
|
||||||
|
DWORD read = 0;
|
||||||
|
ReadFile(file, source.data(), static_cast<DWORD>(source.size()), &read, nullptr);
|
||||||
|
CloseHandle(file);
|
||||||
|
source.resize(read);
|
||||||
|
loaded = modified;
|
||||||
|
scene.setShaderSource(std::move(source));
|
||||||
|
info("shaders reloaded from {}", path.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where the child window goes in the client's 3D view, and where in the 3D view its scene starts (offset).
|
||||||
|
struct Placement {
|
||||||
|
RECT window{};
|
||||||
|
int offset = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Placement placement(HWND parent, View view)
|
||||||
|
{
|
||||||
|
Placement place;
|
||||||
|
GetClientRect(parent, &place.window);
|
||||||
|
if (view == View::Split || view == View::Compare) {
|
||||||
|
place.window.left = place.window.right / 2;
|
||||||
|
}
|
||||||
|
place.offset = view == View::Compare ? 0 : place.window.left;
|
||||||
|
if (view == View::Off) {
|
||||||
|
place.window = {};
|
||||||
|
}
|
||||||
|
return place;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -203,18 +278,25 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Follow the client's 3D view's size.
|
// Follow the client's 3D view's size.
|
||||||
const RECT place = placement(clientWindow, channel->view);
|
const auto [place, offset] = placement(clientWindow, current_view(channel->view));
|
||||||
const int width = place.right - place.left;
|
const int width = place.right - place.left;
|
||||||
const int height = place.bottom - place.top;
|
const int height = place.bottom - place.top;
|
||||||
if (std::memcmp(&place, &placed, sizeof(RECT)) != 0 && width > 0 && height > 0) {
|
if (std::memcmp(&place, &placed, sizeof(RECT)) != 0) {
|
||||||
SetWindowPos(window, nullptr, place.left, place.top, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
|
|
||||||
swapChain->Resize(static_cast<Uint32>(width), static_cast<Uint32>(height));
|
|
||||||
placed = place;
|
placed = place;
|
||||||
info("drawing {}x{} at {} in the client's 3D view", width, height, place.left);
|
if (width > 0 && height > 0) {
|
||||||
|
SetWindowPos(window, nullptr, place.left, place.top, width, height,
|
||||||
|
SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
||||||
|
swapChain->Resize(static_cast<Uint32>(width), static_cast<Uint32>(height));
|
||||||
|
info("drawing {}x{} at {} in the client's 3D view, its scene from {}", width, height, place.left, offset);
|
||||||
|
} else {
|
||||||
|
ShowWindow(window, SW_HIDE);
|
||||||
|
info("hidden");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
reload_shaders(scene);
|
||||||
LARGE_INTEGER start, end;
|
LARGE_INTEGER start, end;
|
||||||
QueryPerformanceCounter(&start);
|
QueryPerformanceCounter(&start);
|
||||||
if (scene.execute(std::span(frame).first(complete), place.left, width > 0 && height > 0)) {
|
if (scene.execute(std::span(frame).first(complete), offset, width > 0 && height > 0)) {
|
||||||
swapChain->Present(0);
|
swapChain->Present(0);
|
||||||
QueryPerformanceCounter(&end);
|
QueryPerformanceCounter(&end);
|
||||||
drawTime += static_cast<double>(end.QuadPart - start.QuadPart) / static_cast<double>(frequency.QuadPart);
|
drawTime += static_cast<double>(end.QuadPart - start.QuadPart) / static_cast<double>(frequency.QuadPart);
|
||||||
|
|||||||
+32
-2
@@ -1,5 +1,8 @@
|
|||||||
#include "scene.hpp"
|
#include "scene.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -56,7 +59,7 @@ constexpr std::uint32_t hasColors = 4;
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Scene::Scene(IRenderDevice* device, IDeviceContext* context, ISwapChain* swapChain)
|
Scene::Scene(IRenderDevice* device, IDeviceContext* context, ISwapChain* swapChain)
|
||||||
: device_(device), context_(context), swapChain_(swapChain)
|
: device_(device), context_(context), swapChain_(swapChain), shaderSource_(fixedFunctionHlsl)
|
||||||
{
|
{
|
||||||
BufferDesc constants;
|
BufferDesc constants;
|
||||||
constants.Name = "Constants";
|
constants.Name = "Constants";
|
||||||
@@ -86,6 +89,13 @@ Scene::Scene(IRenderDevice* device, IDeviceContext* context, ISwapChain* swapCha
|
|||||||
constants_.flags[2] = 127; // the client's alpha reference
|
constants_.flags[2] = 127; // the client's alpha reference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Scene::setShaderSource(std::string source)
|
||||||
|
{
|
||||||
|
shaderSource_ = std::move(source);
|
||||||
|
shaders_.clear();
|
||||||
|
pipelines_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
bool Scene::execute(std::span<const std::byte> commands, int offsetX, bool draw)
|
bool Scene::execute(std::span<const std::byte> commands, int offsetX, bool draw)
|
||||||
{
|
{
|
||||||
offsetX_ = offsetX;
|
offsetX_ = offsetX;
|
||||||
@@ -546,6 +556,26 @@ void Scene::drawPointSprites(const protocol::Command& command)
|
|||||||
constants_.ambient[1], constants_.ambient[2], constants_.texTransform0[0], constants_.texTransform0[1],
|
constants_.ambient[1], constants_.ambient[2], constants_.texTransform0[0], constants_.texTransform0[1],
|
||||||
constants_.texTransform0[2], constants_.texTransform0[4], constants_.texTransform0[5],
|
constants_.texTransform0[2], constants_.texTransform0[4], constants_.texTransform0[5],
|
||||||
constants_.texTransform0[6]);
|
constants_.texTransform0[6]);
|
||||||
|
// The first sprite: where it is, seen from the camera, and how big it's drawn.
|
||||||
|
float point[4] = {0, 0, 0, 1};
|
||||||
|
std::uint32_t color = 0;
|
||||||
|
std::memcpy(point, payload.data(), 12);
|
||||||
|
std::memcpy(&color, payload.data() + 12, 4);
|
||||||
|
const auto transform = [](const float (&p)[4], const float (&m)[16]) {
|
||||||
|
std::array<float, 4> out{};
|
||||||
|
for (int column = 0; column < 4; ++column) {
|
||||||
|
out[column] = p[0] * m[column] + p[1] * m[4 + column] + p[2] * m[8 + column] + p[3] * m[12 + column];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
const auto world = transform(point, constants_.world);
|
||||||
|
const float worldPoint[4] = {world[0], world[1], world[2], world[3]};
|
||||||
|
const auto view = transform(worldPoint, constants_.view);
|
||||||
|
const float distance = std::sqrt(view[0] * view[0] + view[1] * view[1] + view[2] * view[2]);
|
||||||
|
log::info(" first sprite at ({:.2f} {:.2f} {:.2f}), color {:08x}, from the camera ({:.2f} {:.2f} {:.2f}): "
|
||||||
|
"distance {:.2f}, {:.1f} pixels across (viewport height {})",
|
||||||
|
point[0], point[1], point[2], color, view[0], view[1], view[2], distance,
|
||||||
|
std::clamp(constants_.viewport[1] / std::max(distance, 1e-4f), 1.0f, 2048.0f), constants_.viewport[1]);
|
||||||
}
|
}
|
||||||
IBuffer* buffers[] = {buffer};
|
IBuffer* buffers[] = {buffer};
|
||||||
context_->SetVertexBuffers(0, 1, buffers, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION,
|
context_->SetVertexBuffers(0, 1, buffers, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION,
|
||||||
@@ -565,7 +595,7 @@ const Scene::Shaders& Scene::shaders(std::uint32_t streams, std::uint32_t textur
|
|||||||
const std::string source = std::string("#define HAS_NORMALS ") + ((streams & hasNormals) != 0 ? "1" : "0") +
|
const std::string source = std::string("#define HAS_NORMALS ") + ((streams & hasNormals) != 0 ? "1" : "0") +
|
||||||
"\n#define HAS_TEX_COORDS " + ((streams & hasTexCoords) != 0 ? "1" : "0") +
|
"\n#define HAS_TEX_COORDS " + ((streams & hasTexCoords) != 0 ? "1" : "0") +
|
||||||
"\n#define HAS_COLORS " + ((streams & hasColors) != 0 ? "1" : "0") +
|
"\n#define HAS_COLORS " + ((streams & hasColors) != 0 ? "1" : "0") +
|
||||||
"\n#define TEXTURES " + std::to_string(textures) + "\n" + fixedFunctionHlsl;
|
"\n#define TEXTURES " + std::to_string(textures) + "\n" + shaderSource_;
|
||||||
Shaders created;
|
Shaders created;
|
||||||
ShaderCreateInfo info;
|
ShaderCreateInfo info;
|
||||||
info.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL;
|
info.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "EngineFactoryD3D11.h"
|
#include "EngineFactoryD3D11.h"
|
||||||
@@ -23,6 +24,9 @@ public:
|
|||||||
// present).
|
// present).
|
||||||
bool execute(std::span<const std::byte> commands, int offsetX, bool draw);
|
bool execute(std::span<const std::byte> commands, int offsetX, bool draw);
|
||||||
|
|
||||||
|
// Replaces the shaders' source (the built-in one by default): for trying changes while the client runs.
|
||||||
|
void setShaderSource(std::string source);
|
||||||
|
|
||||||
// When the adapter sent the last frame carried out (QueryPerformanceCounter).
|
// When the adapter sent the last frame carried out (QueryPerformanceCounter).
|
||||||
[[nodiscard]] std::uint64_t lastSentAt() const { return lastSentAt_; }
|
[[nodiscard]] std::uint64_t lastSentAt() const { return lastSentAt_; }
|
||||||
|
|
||||||
@@ -111,6 +115,7 @@ private:
|
|||||||
std::unordered_map<std::uint32_t, Buffer> buffers_;
|
std::unordered_map<std::uint32_t, Buffer> buffers_;
|
||||||
std::unordered_map<std::uint32_t, Shaders> shaders_;
|
std::unordered_map<std::uint32_t, Shaders> shaders_;
|
||||||
std::unordered_map<std::uint32_t, Pipeline> pipelines_;
|
std::unordered_map<std::uint32_t, Pipeline> pipelines_;
|
||||||
|
std::string shaderSource_;
|
||||||
|
|
||||||
Constants constants_{};
|
Constants constants_{};
|
||||||
State state_;
|
State state_;
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ void vs_sprite(in SpriteInput input, out PSInput output)
|
|||||||
{
|
{
|
||||||
float4 view = mul(mul(float4(input.Position, 1.0), g_World), g_View);
|
float4 view = mul(mul(float4(input.Position, 1.0), g_World), g_View);
|
||||||
float4 clip = mul(view, g_Projection);
|
float4 clip = mul(view, g_Projection);
|
||||||
float size = clamp(g_Viewport.y / max(length(view.xyz), 1e-4), 1.0, 64.0);
|
float size = clamp(g_Viewport.y / max(length(view.xyz), 1e-4), 1.0, 2048.0);
|
||||||
float2 corner = float2((input.Corner & 1) != 0 ? 1.0 : -1.0, (input.Corner & 2) != 0 ? -1.0 : 1.0);
|
float2 corner = float2((input.Corner & 1) != 0 ? 1.0 : -1.0, (input.Corner & 2) != 0 ? -1.0 : 1.0);
|
||||||
clip.xy += corner * size / g_Viewport.xy * clip.w; // half the size each way, in clip space
|
clip.xy += corner * size / g_Viewport.xy * clip.w; // half the size each way, in clip space
|
||||||
output.Position = clip;
|
output.Position = clip;
|
||||||
|
|||||||
Reference in new issue
Block a user