Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cdda4c7ed | ||
|
|
299cd8c312 |
No files matched your search
+46
-2
@@ -5,6 +5,7 @@
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "log.hpp"
|
||||
#include "resources.hpp"
|
||||
@@ -53,6 +54,9 @@ void Engine::clear(std::uint32_t flags, imvu::Color color)
|
||||
void Engine::setProjection(imvu::Vec2i viewport, const imvu::Mat4f& projection)
|
||||
{
|
||||
inner_->setProjection(viewport, projection);
|
||||
if (projection.m[2][3] == 0.0f) {
|
||||
++stats_.flat;
|
||||
}
|
||||
protocol::SetProjection command{viewport.x, viewport.y, {}};
|
||||
std::memcpy(command.m, &projection.m[0][0], sizeof(command.m));
|
||||
recorder_->add(Op::SetProjection, command);
|
||||
@@ -73,6 +77,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 +229,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});
|
||||
}
|
||||
|
||||
@@ -241,6 +257,14 @@ void Engine::endRendering()
|
||||
recorder_->add(Op::EndFrame, protocol::EndFrame{static_cast<std::uint64_t>(now.QuadPart)});
|
||||
recorder_->flush(true);
|
||||
++frames_;
|
||||
if (frames_ % 300 == 0 && statsLogged_ < 6) {
|
||||
++statsLogged_;
|
||||
log::info("engine: last 300 frames: drawTriangles {} (and {} with buffers not ours), drawTrianglesFromMemory {} "
|
||||
"(and {} without indices), point sprites {}, lines {}, 2D projections {}",
|
||||
stats_.triangles, stats_.foreignTriangles, stats_.indexed, stats_.unindexed, stats_.sprites, stats_.lines,
|
||||
stats_.flat);
|
||||
stats_ = {};
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::drawTriangles(imvu::PiInternalIndexBuffer* indices, imvu::PiInternalVertexBuffer* positions,
|
||||
@@ -248,15 +272,21 @@ void Engine::drawTriangles(imvu::PiInternalIndexBuffer* indices, imvu::PiInterna
|
||||
imvu::PiInternalVertexBuffer* colors)
|
||||
{
|
||||
inner_->drawTriangles(inner(indices), inner(positions), inner(normals), inner(texCoords), inner(colors));
|
||||
++(id(indices) != 0 && id(positions) != 0 ? stats_.triangles : stats_.foreignTriangles);
|
||||
recorder_->add(Op::DrawTriangles,
|
||||
protocol::DrawTriangles{id(indices), id(positions), id(normals), id(texCoords), id(colors)});
|
||||
}
|
||||
|
||||
void Engine::drawLine(imvu::Vec3f from, imvu::Vec3f to, imvu::Color color) { inner_->drawLine(from, to, color); }
|
||||
void Engine::drawLine(imvu::Vec3f from, imvu::Vec3f to, imvu::Color color)
|
||||
{
|
||||
inner_->drawLine(from, to, color);
|
||||
++stats_.lines;
|
||||
}
|
||||
|
||||
void Engine::drawPointSprites(const imvu::PointSpriteVector& sprites)
|
||||
{
|
||||
inner_->drawPointSprites(sprites);
|
||||
++stats_.sprites;
|
||||
if (sprites.first == nullptr || sprites.last <= sprites.first) {
|
||||
return;
|
||||
}
|
||||
@@ -275,9 +305,23 @@ void Engine::drawTrianglesFromMemory(std::uint32_t vertexCount, std::uint32_t in
|
||||
const imvu::VertexAttributeArray<imvu::Color>& colors)
|
||||
{
|
||||
inner_->drawTrianglesFromMemory(vertexCount, indexCount, indices, positions, normals, texCoords, colors);
|
||||
if (indices == nullptr || positions.data == nullptr || vertexCount == 0) {
|
||||
++(indices != nullptr ? stats_.indexed : stats_.unindexed);
|
||||
if (positions.data == nullptr || vertexCount == 0) {
|
||||
return;
|
||||
}
|
||||
// Without indices: the vertices in order, as a triangle list.
|
||||
std::vector<std::uint16_t> sequence;
|
||||
if (indices == nullptr) {
|
||||
if (vertexCount > 0xffff) {
|
||||
return;
|
||||
}
|
||||
sequence.resize(vertexCount - vertexCount % 3);
|
||||
for (std::size_t i = 0; i < sequence.size(); ++i) {
|
||||
sequence[i] = static_cast<std::uint16_t>(i);
|
||||
}
|
||||
indices = sequence.data();
|
||||
indexCount = static_cast<std::uint32_t>(sequence.size());
|
||||
}
|
||||
const bool hasNormals = normals.data != nullptr && normals.count >= vertexCount;
|
||||
const bool hasTexCoords = texCoords.data != nullptr && texCoords.count >= vertexCount;
|
||||
const bool hasColors = colors.data != nullptr && colors.count >= vertexCount;
|
||||
|
||||
@@ -67,6 +67,21 @@ 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
|
||||
|
||||
// Diagnostics: what the client draws, summed over 300 frames and logged (the first few times).
|
||||
struct Stats {
|
||||
std::uint32_t triangles = 0; // drawTriangles with our buffers
|
||||
std::uint32_t foreignTriangles = 0; // drawTriangles with buffers that aren't ours (not drawn)
|
||||
std::uint32_t indexed = 0; // drawTrianglesFromMemory with indices
|
||||
std::uint32_t unindexed = 0; // drawTrianglesFromMemory without
|
||||
std::uint32_t sprites = 0;
|
||||
std::uint32_t lines = 0;
|
||||
std::uint32_t flat = 0; // setProjection with an orthographic projection (2D)
|
||||
};
|
||||
Stats stats_;
|
||||
int statsLogged_ = 0;
|
||||
|
||||
};
|
||||
|
||||
} // 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
|
||||
+15
-2
@@ -36,8 +36,21 @@ client exits.
|
||||
## Status
|
||||
|
||||
The renderer process draws the client's whole scene, over the right half of the 3D view, beside the client's own
|
||||
Direct3D renderer on the left (the adapter still forwards everything to it): `T5DE_RENDERER_VIEW=full` or `off`
|
||||
changes that. Checked in rooms: the halves match, particles included.
|
||||
Direct3D renderer on the left (the adapter still forwards everything to it). Checked in rooms with screenshots of
|
||||
both renderers side by side (the compare view): they match, particles and the 2D overlay (name tags) included.
|
||||
|
||||
For comparing while the client runs (development):
|
||||
|
||||
- Views: `T5DE_RENDERER_VIEW` (off, split, full or compare) when the client starts, or
|
||||
`%TEMP%\t5de-renderer-view.txt` holding one of those at any time. Compare draws the left half's scene over the
|
||||
right half, so both renderers show the same thing side by side.
|
||||
- `%TEMP%\t5de-renderer-shaders\fixed_function.hlsl`, if present, replaces the built-in shaders and is reloaded
|
||||
when it changes.
|
||||
- The renderer process runs from a copy (`%TEMP%\t5de-renderer`), so `t5de-renderer.exe` can be replaced while the
|
||||
client runs; engines started afterwards run the new one (the room's view keeps its engine until the client
|
||||
restarts).
|
||||
- The adapter's log (`%TEMP%\t5de-renderer.log`) sums up what the client draws every 300 frames (the first few
|
||||
times), and logs textures and render targets the client renderer made itself.
|
||||
|
||||
- The command stream (`include/t5de/protocol/commands.hpp`) carries state, transforms, lights, fog, textures,
|
||||
buffers (copied when created or updated) and draws, in chunks through the channel; the adapter waits for each to
|
||||
|
||||
+9
-2
@@ -71,9 +71,16 @@ virtual methods, IMVU's code only uses an engine through helpers that call its v
|
||||
vertical field of view, Direct3D's 0 to 1 depth) and an orthographic one for 2D, orthonormal view rotations,
|
||||
opaque clear colors, ambient light with zero alpha, linear fog (1 to 50), scrolling texture transforms. Furniture
|
||||
vertices are interleaved: positions and normals are 12-byte elements 32 bytes apart, so strides matter.
|
||||
- Projections give depth from -1 to 1, as OpenGL's do: IMVU's Direct3D renderer multiplies them by a matrix
|
||||
mapping it to 0 to 1 (z' = z / 2 + w / 2) and shifting them half a pixel (-1 / width, +1 / height, for Direct3D
|
||||
9's pixel centers). Without the depth mapping, everything in the nearer half of the depth range is clipped: the
|
||||
2D overlay (name tags, the frame rate, the room's title) is drawn at depth -0.98.
|
||||
- The 2D overlay is ordinary `drawTriangles` with an orthographic projection (a few per frame), textured with text
|
||||
IMVU draws with GDI into bitmaps.
|
||||
- Particles (`drawPointSprites`): a Visual C++ 2010 `std::vector` of 16-byte points (position, color), drawn as
|
||||
Direct3D 9 point sprites: squares viewport height / distance pixels across (Direct3D 9's defaults clamp them to
|
||||
1 to 64; IMVU sets only the size, 1, and the scale factors 0, 0, 1), textured over the whole square.
|
||||
Direct3D 9 point sprites: squares viewport height / distance pixels across (IMVU sets the size, 1, and the scale
|
||||
factors 0, 0, 1), textured over the whole square. Direct3D 9's documented 64-pixel default limit doesn't apply in
|
||||
practice: clamped to it they look much smaller and fainter than the client's.
|
||||
- Fog: linear table fog, start 1 and end 50 in rooms. Fogging by view depth matches the client; by depth buffer
|
||||
value (as Direct3D 9's documentation suggests for projections like IMVU's) doesn't.
|
||||
- Still unknown: render targets, `CompoundRegion` (texture update
|
||||
|
||||
@@ -14,6 +14,7 @@ enum class View : std::uint32_t {
|
||||
Off, // none
|
||||
Split, // the right half, to compare with the client's renderer on the left
|
||||
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>`
|
||||
|
||||
+95
-13
@@ -84,15 +84,90 @@ HWND create_child_window(HWND parent)
|
||||
0, 0, 1, 1, parent, nullptr, windowClass.hInstance, nullptr);
|
||||
}
|
||||
|
||||
// Where the child window goes in the client's 3D view.
|
||||
RECT placement(HWND parent, View view)
|
||||
// The view, as the adapter set it, or as %TEMP%\t5de-renderer-view.txt says while the client runs (off, split, full
|
||||
// or compare): for comparing the renderers without restarting the client. Read every second or so.
|
||||
View current_view(View adapter)
|
||||
{
|
||||
RECT client{};
|
||||
GetClientRect(parent, &client);
|
||||
if (view == View::Split) {
|
||||
client.left = client.right / 2;
|
||||
static View view = adapter;
|
||||
static ULONGLONG checked = 0;
|
||||
if (GetTickCount64() - checked < 1000) {
|
||||
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
|
||||
@@ -203,18 +278,25 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||
}
|
||||
|
||||
// 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 height = place.bottom - place.top;
|
||||
if (std::memcmp(&place, &placed, sizeof(RECT)) != 0 && width > 0 && height > 0) {
|
||||
SetWindowPos(window, nullptr, place.left, place.top, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
swapChain->Resize(static_cast<Uint32>(width), static_cast<Uint32>(height));
|
||||
if (std::memcmp(&place, &placed, sizeof(RECT)) != 0) {
|
||||
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;
|
||||
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);
|
||||
QueryPerformanceCounter(&end);
|
||||
drawTime += static_cast<double>(end.QuadPart - start.QuadPart) / static_cast<double>(frequency.QuadPart);
|
||||
|
||||
+16
-15
@@ -4,7 +4,6 @@
|
||||
#include <string>
|
||||
|
||||
#include "fixed_function_hlsl.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
namespace t5de {
|
||||
|
||||
@@ -56,7 +55,7 @@ constexpr std::uint32_t hasColors = 4;
|
||||
} // namespace
|
||||
|
||||
Scene::Scene(IRenderDevice* device, IDeviceContext* context, ISwapChain* swapChain)
|
||||
: device_(device), context_(context), swapChain_(swapChain)
|
||||
: device_(device), context_(context), swapChain_(swapChain), shaderSource_(fixedFunctionHlsl)
|
||||
{
|
||||
BufferDesc constants;
|
||||
constants.Name = "Constants";
|
||||
@@ -86,6 +85,13 @@ Scene::Scene(IRenderDevice* device, IDeviceContext* context, ISwapChain* swapCha
|
||||
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)
|
||||
{
|
||||
offsetX_ = offsetX;
|
||||
@@ -217,6 +223,13 @@ bool Scene::execute(std::span<const std::byte> commands, int offsetX, bool draw)
|
||||
void Scene::setProjection(const protocol::SetProjection& projection)
|
||||
{
|
||||
std::memcpy(constants_.projection, projection.m, sizeof(constants_.projection));
|
||||
// The client's projections give depth from -1 to 1, as OpenGL does; IMVU's Direct3D renderer maps it to 0 to 1
|
||||
// (z' = z / 2 + w / 2) before setting them, and so do we. (It also shifts them half a pixel, which Direct3D 9
|
||||
// needs and Direct3D 11 doesn't.)
|
||||
for (int row = 0; row < 4; ++row) {
|
||||
float* m = constants_.projection + row * 4;
|
||||
m[2] = 0.5f * m[2] + 0.5f * m[3];
|
||||
}
|
||||
constants_.viewport[0] = static_cast<float>(projection.width);
|
||||
constants_.viewport[1] = static_cast<float>(projection.height);
|
||||
if (!onscreen_) {
|
||||
@@ -535,18 +548,6 @@ void Scene::drawPointSprites(const protocol::Command& command)
|
||||
if (!buffer || prepare(0, true) == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (spritesLogged_ < 5) {
|
||||
++spritesLogged_;
|
||||
const Texture* texture = textures_.contains(state_.texture0) ? &textures_[state_.texture0] : nullptr;
|
||||
log::info("particles: {}, texture {} {}x{} flags {}, blending {} mode {}, depth test {} write {}, alpha test {} "
|
||||
"reference {}, lighting {}, fog {}, ambient ({:.2f} {:.2f} {:.2f}), texture transform [{} {} {} {} {} {}]",
|
||||
sprites->count, state_.texture0, texture ? texture->width : 0, texture ? texture->height : 0,
|
||||
texture ? texture->flags : 0, state_.blending, state_.blendMode, state_.depthTest, state_.depthWrite,
|
||||
constants_.flags[1], constants_.flags[2], constants_.flags[0], constants_.fog[2], constants_.ambient[0],
|
||||
constants_.ambient[1], constants_.ambient[2], constants_.texTransform0[0], constants_.texTransform0[1],
|
||||
constants_.texTransform0[2], constants_.texTransform0[4], constants_.texTransform0[5],
|
||||
constants_.texTransform0[6]);
|
||||
}
|
||||
IBuffer* buffers[] = {buffer};
|
||||
context_->SetVertexBuffers(0, 1, buffers, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION,
|
||||
SET_VERTEX_BUFFERS_FLAG_RESET);
|
||||
@@ -565,7 +566,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") +
|
||||
"\n#define HAS_TEX_COORDS " + ((streams & hasTexCoords) != 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;
|
||||
ShaderCreateInfo info;
|
||||
info.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL;
|
||||
|
||||
+5
-1
@@ -6,6 +6,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "EngineFactoryD3D11.h"
|
||||
@@ -23,6 +24,9 @@ public:
|
||||
// present).
|
||||
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).
|
||||
[[nodiscard]] std::uint64_t lastSentAt() const { return lastSentAt_; }
|
||||
|
||||
@@ -111,13 +115,13 @@ private:
|
||||
std::unordered_map<std::uint32_t, Buffer> buffers_;
|
||||
std::unordered_map<std::uint32_t, Shaders> shaders_;
|
||||
std::unordered_map<std::uint32_t, Pipeline> pipelines_;
|
||||
std::string shaderSource_;
|
||||
|
||||
Constants constants_{};
|
||||
State state_;
|
||||
bool onscreen_ = false; // in a frame drawn into the window
|
||||
bool draw_ = true;
|
||||
std::uint64_t lastSentAt_ = 0;
|
||||
int spritesLogged_ = 0;
|
||||
int offsetX_ = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -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 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);
|
||||
clip.xy += corner * size / g_Viewport.xy * clip.w; // half the size each way, in clip space
|
||||
output.Position = clip;
|
||||
|
||||
Reference in new issue
Block a user