Draw particles, match the client's fog, and measure frame delay
Particles (point sprites) are drawn as instanced camera-facing squares sized like Direct3D 9's. Fog is by depth buffer value, as Direct3D 9 does with IMVU's projections. At most one frame is queued for display, frames carry the adapter's timestamp so the renderer process logs the delay to presenting, and the log's lines from several processes no longer mix. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
42fc55cb91
commit
a04dba18c1
12 files changed
+291
-88
No files matched your search
+17
-2
@@ -1,5 +1,7 @@
|
||||
#include "engine.hpp"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
@@ -234,7 +236,9 @@ void Engine::beginRendering(imvu::PiRenderTarget* target)
|
||||
void Engine::endRendering()
|
||||
{
|
||||
inner_->endRendering();
|
||||
recorder_->add(Op::EndFrame, protocol::EndFrame{});
|
||||
LARGE_INTEGER now;
|
||||
QueryPerformanceCounter(&now);
|
||||
recorder_->add(Op::EndFrame, protocol::EndFrame{static_cast<std::uint64_t>(now.QuadPart)});
|
||||
recorder_->flush(true);
|
||||
++frames_;
|
||||
}
|
||||
@@ -250,7 +254,18 @@ void Engine::drawTriangles(imvu::PiInternalIndexBuffer* indices, imvu::PiInterna
|
||||
|
||||
void Engine::drawLine(imvu::Vec3f from, imvu::Vec3f to, imvu::Color color) { inner_->drawLine(from, to, color); }
|
||||
|
||||
void Engine::drawPointSprites(const imvu::PointSpriteVector& sprites) { inner_->drawPointSprites(sprites); }
|
||||
void Engine::drawPointSprites(const imvu::PointSpriteVector& sprites)
|
||||
{
|
||||
inner_->drawPointSprites(sprites);
|
||||
if (sprites.first == nullptr || sprites.last <= sprites.first) {
|
||||
return;
|
||||
}
|
||||
const auto count = static_cast<std::uint32_t>(sprites.last - sprites.first);
|
||||
if (std::byte* payload = recorder_->add(Op::DrawPointSprites, protocol::DrawPointSprites{count},
|
||||
std::size_t{count} * sizeof(imvu::PointSprite))) {
|
||||
std::memcpy(payload, sprites.first, std::size_t{count} * sizeof(imvu::PointSprite));
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::drawTrianglesFromMemory(std::uint32_t vertexCount, std::uint32_t indexCount,
|
||||
const std::uint16_t* indices,
|
||||
|
||||
+16
-8
@@ -35,15 +35,23 @@ client exits.
|
||||
|
||||
## Status
|
||||
|
||||
A spike: the adapter still has IMVU's Direct3D 9 engine draw the scene (the pass-through), and the renderer process
|
||||
only draws a box in the 3D view's corner, pulsing with the client's frames. Checked in the client (Direct3D 11 in
|
||||
software, on a machine without a graphics driver): the box draws over the scene from the 64-bit process, keeps in
|
||||
step with the client's frames, clicks on it reach the scene, and each renderer process exits with its engine (room
|
||||
changes) and with the client. The client keeps two engines alive in a room, so two processes (about 15 MB each);
|
||||
one process serving all of a client's engines would be leaner.
|
||||
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.
|
||||
|
||||
Next: the protocol carrying each frame's calls and the resources (buffers, textures), and the renderer process
|
||||
drawing the scene itself, with IMVU's Direct3D engine left as the fallback.
|
||||
- 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
|
||||
be taken, and gives up on a renderer process that stops taking them (the client's renderer carries on).
|
||||
- The renderer process reproduces IMVU's Direct3D 9 setup (`renderer/shaders/fixed_function.hlsl`), particles
|
||||
included (point sprites, drawn as instanced squares), with a pipeline per combination of states.
|
||||
- Speed: on the development VM (software rendering) a room frame takes 8 to 60 ms to draw; the time from the
|
||||
client finishing a frame to ours being presented averages about 60 ms. The renderer process draws only the
|
||||
newest frame when it falls behind, and keeps at most one frame queued. Over Remote Desktop, the half drawn by the
|
||||
renderer process (another process's window, composited by Windows) visibly trails the client's half by more
|
||||
than that; that delay comes after presenting, and is to be checked on a real graphics card and display.
|
||||
|
||||
Next: render targets (offscreen frames are skipped), the renderer process drawing the whole view in place of the
|
||||
client's renderer, and one renderer process for all of a client's views.
|
||||
|
||||
## Building
|
||||
|
||||
|
||||
+6
-1
@@ -71,7 +71,12 @@ 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.
|
||||
- Still unknown: point sprites (`drawPointSprites`, particles), render targets, `CompoundRegion` (texture update
|
||||
- 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.
|
||||
- Fog: linear table fog. Direct3D 9 fogs by eye distance only for projections whose (3, 4) element is +1; IMVU's are
|
||||
right-handed (-1), so it fogs by depth buffer value (0 to 1) against start 1 and end 50: in effect no fog.
|
||||
- Still unknown: render targets, `CompoundRegion` (texture update
|
||||
regions), `newRenderTexture`'s flags, and what `beginRendering` does with a null target. MSVC orders overloaded
|
||||
virtual methods unlike their declaration, so our declarations never overload virtual methods.
|
||||
- Memory crosses the boundary only through virtual methods (destructors, control blocks), so each side can use its
|
||||
|
||||
@@ -90,8 +90,21 @@ static_assert(sizeof(VertexAttributeArray<Vec3f>) == 12);
|
||||
// (width, height, pitch, rowBytes, pixels, ...).
|
||||
class Pixmap;
|
||||
|
||||
// Unknown so far: used only by drawPointSprites (particles), as a Visual C++ 2010 std::vector<PointSprite>.
|
||||
struct PointSpriteVector;
|
||||
// A particle (drawPointSprites): a point drawn as a camera-facing square, textured, about viewport height / distance
|
||||
// pixels across (1 to 64), as the Direct3D engine's point sprites are.
|
||||
struct PointSprite {
|
||||
Vec3f position;
|
||||
Color color;
|
||||
};
|
||||
|
||||
static_assert(sizeof(PointSprite) == 16);
|
||||
|
||||
// std::vector<PointSprite> as Visual C++ 2010 lays it out (release builds): its elements are [first, last).
|
||||
struct PointSpriteVector {
|
||||
const PointSprite* first;
|
||||
const PointSprite* last;
|
||||
const PointSprite* end; // of the allocation
|
||||
};
|
||||
|
||||
// Region lists IMVU passes to texture updates; unknown so far.
|
||||
class CompoundRegion;
|
||||
|
||||
@@ -32,6 +32,7 @@ enum class Op : std::uint16_t {
|
||||
DestroyBuffer,
|
||||
DrawTriangles,
|
||||
DrawArrays,
|
||||
DrawPointSprites,
|
||||
};
|
||||
|
||||
struct Header {
|
||||
@@ -47,7 +48,7 @@ struct BeginFrame {
|
||||
};
|
||||
|
||||
struct EndFrame {
|
||||
std::uint32_t unused;
|
||||
std::uint64_t sentAt; // the adapter's QueryPerformanceCounter (the same clock in every process)
|
||||
};
|
||||
|
||||
struct Clear {
|
||||
@@ -172,6 +173,12 @@ struct DrawArrays {
|
||||
std::uint32_t hasColors;
|
||||
};
|
||||
|
||||
// Particles: payload count sprites of 16 bytes, a position (3 floats) and a color. Each is a camera-facing square,
|
||||
// viewport height / distance pixels across (1 to 64), textured with the first texture over the whole square.
|
||||
struct DrawPointSprites {
|
||||
std::uint32_t count;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Header) == 8 && sizeof(SetProjection) == 72 && sizeof(SetLight) == 28
|
||||
&& sizeof(DrawArrays) == 20);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ function(t5de_embed_shader file name)
|
||||
endfunction()
|
||||
t5de_embed_shader(fixed_function.hlsl fixedFunctionHlsl)
|
||||
|
||||
add_executable(t5de_renderer WIN32 main.cpp scene.cpp)
|
||||
add_executable(t5de_renderer WIN32 log.cpp main.cpp scene.cpp)
|
||||
set_target_properties(t5de_renderer PROPERTIES OUTPUT_NAME t5de-renderer)
|
||||
target_include_directories(t5de_renderer PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated)
|
||||
target_link_libraries(t5de_renderer PRIVATE t5de::protocol t5de::options Diligent-GraphicsEngineD3D11-static Diligent-Common)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "log.hpp"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace t5de::log {
|
||||
|
||||
// Each line is one appending write, so lines from several processes don't mix.
|
||||
void write(std::string_view line)
|
||||
{
|
||||
static const HANDLE file = [] {
|
||||
wchar_t temp[MAX_PATH + 1];
|
||||
const DWORD length = GetTempPathW(MAX_PATH + 1, temp);
|
||||
if (length == 0 || length > MAX_PATH) {
|
||||
return INVALID_HANDLE_VALUE;
|
||||
}
|
||||
return CreateFileW((std::filesystem::path(temp) / L"t5de-renderer-process.log").c_str(), FILE_APPEND_DATA,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
}();
|
||||
if (file != INVALID_HANDLE_VALUE) {
|
||||
const std::string text = std::format("{}: {}\n", GetCurrentProcessId(), line);
|
||||
DWORD written = 0;
|
||||
WriteFile(file, text.data(), static_cast<DWORD>(text.size()), &written, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace t5de::log
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
// The renderer processes' log: %TEMP%\t5de-renderer-process.log, shared by all of them (lines start with the pid).
|
||||
|
||||
#include <format>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace t5de::log {
|
||||
|
||||
void write(std::string_view line);
|
||||
|
||||
template <class... Args>
|
||||
void info(std::format_string<Args...> format, Args&&... args)
|
||||
{
|
||||
write(std::format(format, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
} // namespace t5de::log
|
||||
+26
-31
@@ -5,11 +5,11 @@
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <share.h>
|
||||
#include <d3d11.h>
|
||||
#include <dxgi.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include "EngineFactoryD3D11.h"
|
||||
#include "RefCntAutoPtr.hpp"
|
||||
#include "RenderDeviceD3D11.h"
|
||||
#include "log.hpp"
|
||||
#include "scene.hpp"
|
||||
#include "t5de/protocol/channel.hpp"
|
||||
|
||||
@@ -28,34 +30,14 @@ using namespace Diligent;
|
||||
using t5de::protocol::Channel;
|
||||
using t5de::protocol::View;
|
||||
|
||||
// The renderer processes' log: %TEMP%\t5de-renderer-process.log, shared by all of them (lines start with the pid).
|
||||
void write_log(std::string_view line)
|
||||
{
|
||||
static std::FILE* file = [] {
|
||||
wchar_t temp[MAX_PATH + 1];
|
||||
const DWORD length = GetTempPathW(MAX_PATH + 1, temp);
|
||||
return length == 0 || length > MAX_PATH
|
||||
? nullptr
|
||||
: _wfsopen((std::filesystem::path(temp) / L"t5de-renderer-process.log").c_str(), L"a", _SH_DENYNO);
|
||||
}();
|
||||
if (file != nullptr) {
|
||||
std::fprintf(file, "%lu: %.*s\n", GetCurrentProcessId(), static_cast<int>(line.size()), line.data());
|
||||
std::fflush(file);
|
||||
}
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void log(std::format_string<Args...> format, Args&&... args)
|
||||
{
|
||||
write_log(std::format(format, std::forward<Args>(args)...));
|
||||
}
|
||||
using t5de::log::info;
|
||||
|
||||
// Diligent's messages (shader compilation errors, invalid states, ...), into the log.
|
||||
void DILIGENT_CALL_TYPE diligent_message(DEBUG_MESSAGE_SEVERITY severity, const Char* message, const Char* function,
|
||||
const Char*, int)
|
||||
{
|
||||
static constexpr const char* severities[] = {"info", "warning", "error", "fatal error"};
|
||||
log("Diligent {}: {}{}{}", severities[severity < 4 ? severity : 3], function != nullptr ? function : "",
|
||||
info("Diligent {}: {}{}{}", severities[severity < 4 ? severity : 3], function != nullptr ? function : "",
|
||||
function != nullptr ? ": " : "", message != nullptr ? message : "");
|
||||
}
|
||||
|
||||
@@ -140,7 +122,7 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||
return 4;
|
||||
}
|
||||
|
||||
log("started for window {:#x}, view {}", channel->window, static_cast<int>(channel->view));
|
||||
info("started for window {:#x}, view {}", channel->window, static_cast<int>(channel->view));
|
||||
IEngineFactoryD3D11* factory = GetEngineFactoryD3D11();
|
||||
factory->SetMessageCallback(diligent_message);
|
||||
RefCntAutoPtr<IRenderDevice> device;
|
||||
@@ -148,9 +130,17 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||
EngineD3D11CreateInfo engineInfo;
|
||||
factory->CreateDeviceAndContextsD3D11(engineInfo, &device, &context);
|
||||
if (!device) {
|
||||
log("can't create a Direct3D 11 device");
|
||||
info("can't create a Direct3D 11 device");
|
||||
return 5;
|
||||
}
|
||||
// Show each frame as soon as it's drawn: Direct3D 11 otherwise lets 3 frames queue, which the view would trail by.
|
||||
if (RefCntAutoPtr<IRenderDeviceD3D11> d3d11{device, IID_RenderDeviceD3D11}) {
|
||||
IDXGIDevice1* dxgi = nullptr;
|
||||
if (SUCCEEDED(d3d11->GetD3D11Device()->QueryInterface(__uuidof(IDXGIDevice1), reinterpret_cast<void**>(&dxgi)))) {
|
||||
dxgi->SetMaximumFrameLatency(1);
|
||||
dxgi->Release();
|
||||
}
|
||||
}
|
||||
RefCntAutoPtr<ISwapChain> swapChain;
|
||||
SwapChainDesc swapChainDesc;
|
||||
swapChainDesc.ColorBufferFormat = TEX_FORMAT_RGBA8_UNORM; // no sRGB conversion, like the client's renderer
|
||||
@@ -158,7 +148,7 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||
factory->CreateSwapChainD3D11(device, context, swapChainDesc, FullScreenModeDesc{}, Win32NativeWindow{window},
|
||||
&swapChain);
|
||||
if (!swapChain) {
|
||||
log("can't create a swap chain");
|
||||
info("can't create a swap chain");
|
||||
return 6;
|
||||
}
|
||||
t5de::Scene scene(device, context, swapChain);
|
||||
@@ -177,6 +167,7 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||
LARGE_INTEGER frequency;
|
||||
QueryPerformanceFrequency(&frequency);
|
||||
double drawTime = 0;
|
||||
double latency = 0;
|
||||
int drawn = 0;
|
||||
int skipped = 0;
|
||||
RECT placed{};
|
||||
@@ -219,7 +210,7 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||
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;
|
||||
log("drawing {}x{} at {} in the client's 3D view", width, height, place.left);
|
||||
info("drawing {}x{} at {} in the client's 3D view", width, height, place.left);
|
||||
}
|
||||
LARGE_INTEGER start, end;
|
||||
QueryPerformanceCounter(&start);
|
||||
@@ -227,10 +218,14 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||
swapChain->Present(0);
|
||||
QueryPerformanceCounter(&end);
|
||||
drawTime += static_cast<double>(end.QuadPart - start.QuadPart) / static_cast<double>(frequency.QuadPart);
|
||||
latency += static_cast<double>(end.QuadPart - static_cast<LONGLONG>(scene.lastSentAt())) /
|
||||
static_cast<double>(frequency.QuadPart);
|
||||
if (++drawn == 300) {
|
||||
log("last 300 frames: {:.1f} ms each to draw and present, {} skipped to catch up", drawTime * 1000 / drawn,
|
||||
skipped);
|
||||
info("last 300 frames: {:.1f} ms each to draw and present, {:.1f} ms from the client's end of frame to "
|
||||
"presented, {} skipped to catch up",
|
||||
drawTime * 1000 / drawn, latency * 1000 / drawn, skipped);
|
||||
drawTime = 0;
|
||||
latency = 0;
|
||||
drawn = 0;
|
||||
skipped = 0;
|
||||
}
|
||||
@@ -238,7 +233,7 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||
frame.erase(frame.begin(), frame.begin() + static_cast<std::ptrdiff_t>(complete));
|
||||
}
|
||||
|
||||
log("exiting");
|
||||
info("exiting");
|
||||
DestroyWindow(window);
|
||||
return 0;
|
||||
}
|
||||
+110
-36
@@ -4,6 +4,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "fixed_function_hlsl.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
namespace t5de {
|
||||
|
||||
@@ -104,6 +105,9 @@ bool Scene::execute(std::span<const std::byte> commands, int offsetX, bool draw)
|
||||
}
|
||||
break;
|
||||
case Op::EndFrame:
|
||||
if (const auto end = command->body<protocol::EndFrame>()) {
|
||||
lastSentAt_ = end->sentAt;
|
||||
}
|
||||
drew = drew || onscreen_;
|
||||
onscreen_ = false;
|
||||
break;
|
||||
@@ -200,6 +204,11 @@ bool Scene::execute(std::span<const std::byte> commands, int offsetX, bool draw)
|
||||
drawArrays(*command);
|
||||
}
|
||||
break;
|
||||
case Op::DrawPointSprites:
|
||||
if (onscreen_) {
|
||||
drawPointSprites(*command);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return drew;
|
||||
@@ -208,6 +217,8 @@ 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));
|
||||
constants_.viewport[0] = static_cast<float>(projection.width);
|
||||
constants_.viewport[1] = static_cast<float>(projection.height);
|
||||
if (!onscreen_) {
|
||||
return;
|
||||
}
|
||||
@@ -452,34 +463,12 @@ void Scene::draw(IBuffer* indices, std::uint32_t indexCount, IBuffer* positions,
|
||||
if (indices == nullptr || positions == nullptr || indexCount == 0) {
|
||||
return;
|
||||
}
|
||||
// Like the client: no textures, the first, or both (the second only with a first).
|
||||
ITextureView* texture0 = textureView(state_.texture0);
|
||||
ITextureView* texture1 = texture0 != nullptr ? textureView(state_.texture1) : nullptr;
|
||||
const std::uint32_t textures = texture0 == nullptr ? 0 : texture1 == nullptr ? 1 : 2;
|
||||
const std::uint32_t streams =
|
||||
(normals != nullptr ? hasNormals : 0) | (texCoords != nullptr ? hasTexCoords : 0) | (colors != nullptr ? hasColors : 0);
|
||||
Pipeline* selected = pipeline(streams, textures);
|
||||
if (selected == nullptr) {
|
||||
if (prepare(streams, false) == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
void* mapped = nullptr;
|
||||
context_->MapBuffer(constantBuffer_, MAP_WRITE, MAP_FLAG_DISCARD, mapped);
|
||||
if (mapped == nullptr) {
|
||||
return;
|
||||
}
|
||||
std::memcpy(mapped, &constants_, sizeof(constants_));
|
||||
context_->UnmapBuffer(constantBuffer_, MAP_WRITE);
|
||||
|
||||
if (selected->texture0 != nullptr) {
|
||||
selected->texture0->Set(texture0);
|
||||
}
|
||||
if (selected->texture1 != nullptr) {
|
||||
selected->texture1->Set(texture1);
|
||||
}
|
||||
context_->SetPipelineState(selected->state);
|
||||
context_->CommitShaderResources(selected->bindings, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||
|
||||
IBuffer* vertexBuffers[4];
|
||||
std::uint32_t count = 0;
|
||||
for (IBuffer* stream : {positions, normals, texCoords, colors}) {
|
||||
@@ -496,9 +485,80 @@ void Scene::draw(IBuffer* indices, std::uint32_t indexCount, IBuffer* positions,
|
||||
context_->DrawIndexed(attributes);
|
||||
}
|
||||
|
||||
const Scene::Shaders& Scene::shaders(std::uint32_t streams, std::uint32_t textures)
|
||||
Scene::Pipeline* Scene::prepare(std::uint32_t streams, bool sprites)
|
||||
{
|
||||
const std::uint32_t key = streams | (textures << 3);
|
||||
// Like the client: no textures, the first, or both (the second only with a first).
|
||||
ITextureView* texture0 = textureView(state_.texture0);
|
||||
ITextureView* texture1 = texture0 != nullptr ? textureView(state_.texture1) : nullptr;
|
||||
const std::uint32_t textures = texture0 == nullptr ? 0 : texture1 == nullptr ? 1 : 2;
|
||||
Pipeline* selected = pipeline(streams, textures, sprites);
|
||||
if (selected == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* mapped = nullptr;
|
||||
context_->MapBuffer(constantBuffer_, MAP_WRITE, MAP_FLAG_DISCARD, mapped);
|
||||
if (mapped == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
std::memcpy(mapped, &constants_, sizeof(constants_));
|
||||
context_->UnmapBuffer(constantBuffer_, MAP_WRITE);
|
||||
|
||||
if (selected->texture0 != nullptr) {
|
||||
selected->texture0->Set(texture0);
|
||||
}
|
||||
if (selected->texture1 != nullptr) {
|
||||
selected->texture1->Set(texture1);
|
||||
}
|
||||
context_->SetPipelineState(selected->state);
|
||||
context_->CommitShaderResources(selected->bindings, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||
return selected;
|
||||
}
|
||||
|
||||
void Scene::drawPointSprites(const protocol::Command& command)
|
||||
{
|
||||
const auto sprites = command.body<protocol::DrawPointSprites>();
|
||||
const auto payload = command.payload<protocol::DrawPointSprites>();
|
||||
if (!sprites || sprites->count == 0 || payload.size() < std::size_t{sprites->count} * 16) {
|
||||
return;
|
||||
}
|
||||
BufferDesc desc;
|
||||
desc.Name = "Client particles";
|
||||
desc.Size = std::size_t{sprites->count} * 16;
|
||||
desc.BindFlags = BIND_VERTEX_BUFFER;
|
||||
desc.Usage = USAGE_IMMUTABLE;
|
||||
BufferData contents;
|
||||
contents.pData = payload.data();
|
||||
contents.DataSize = desc.Size;
|
||||
RefCntAutoPtr<IBuffer> buffer;
|
||||
device_->CreateBuffer(desc, &contents, &buffer);
|
||||
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);
|
||||
DrawAttribs attributes;
|
||||
attributes.NumVertices = 4;
|
||||
attributes.NumInstances = sprites->count;
|
||||
context_->Draw(attributes);
|
||||
}
|
||||
|
||||
const Scene::Shaders& Scene::shaders(std::uint32_t streams, std::uint32_t textures, bool sprites)
|
||||
{
|
||||
const std::uint32_t key = streams | (textures << 3) | (sprites ? 1u << 5 : 0);
|
||||
if (const auto found = shaders_.find(key); found != shaders_.end()) {
|
||||
return found->second;
|
||||
}
|
||||
@@ -512,8 +572,8 @@ const Scene::Shaders& Scene::shaders(std::uint32_t streams, std::uint32_t textur
|
||||
info.Desc.UseCombinedTextureSamplers = true; // g_TextureN with g_TextureN_sampler, set from each texture's view
|
||||
info.Source = source.c_str();
|
||||
info.Desc.ShaderType = SHADER_TYPE_VERTEX;
|
||||
info.Desc.Name = "Fixed function (vertex)";
|
||||
info.EntryPoint = "vs_main";
|
||||
info.Desc.Name = sprites ? "Fixed function particles (vertex)" : "Fixed function (vertex)";
|
||||
info.EntryPoint = sprites ? "vs_sprite" : "vs_main";
|
||||
device_->CreateShader(info, &created.vertex);
|
||||
info.Desc.ShaderType = SHADER_TYPE_PIXEL;
|
||||
info.Desc.Name = "Fixed function (pixel)";
|
||||
@@ -522,17 +582,18 @@ const Scene::Shaders& Scene::shaders(std::uint32_t streams, std::uint32_t textur
|
||||
return shaders_[key] = std::move(created);
|
||||
}
|
||||
|
||||
Scene::Pipeline* Scene::pipeline(std::uint32_t streams, std::uint32_t textures)
|
||||
Scene::Pipeline* Scene::pipeline(std::uint32_t streams, std::uint32_t textures, bool sprites)
|
||||
{
|
||||
const std::uint32_t blend = state_.blending ? state_.blendMode : 0;
|
||||
const bool culling = state_.culling && !sprites; // point sprites aren't culled
|
||||
const std::uint32_t key = streams | (textures << 3) | (blend << 5) | (state_.depthTest ? 1u << 8 : 0) |
|
||||
(state_.depthWrite ? 1u << 9 : 0) | (state_.culling ? 1u << 10 : 0) |
|
||||
(state_.normalInverting ? 1u << 11 : 0);
|
||||
(state_.depthWrite ? 1u << 9 : 0) | (culling ? 1u << 10 : 0) |
|
||||
(state_.normalInverting ? 1u << 11 : 0) | (sprites ? 1u << 12 : 0);
|
||||
if (const auto found = pipelines_.find(key); found != pipelines_.end()) {
|
||||
return found->second.state ? &found->second : nullptr;
|
||||
}
|
||||
Pipeline& entry = pipelines_[key];
|
||||
const Shaders& compiled = shaders(streams, textures);
|
||||
const Shaders& compiled = shaders(streams, textures, sprites);
|
||||
if (!compiled.vertex || !compiled.pixel) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -545,9 +606,9 @@ Scene::Pipeline* Scene::pipeline(std::uint32_t streams, std::uint32_t textures)
|
||||
graphics.NumRenderTargets = 1;
|
||||
graphics.RTVFormats[0] = window.ColorBufferFormat;
|
||||
graphics.DSVFormat = window.DepthBufferFormat;
|
||||
graphics.PrimitiveTopology = PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
graphics.PrimitiveTopology = sprites ? PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP : PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
// The client culls clockwise triangles, or counter-clockwise ones when normals are inverted (mirrored objects).
|
||||
graphics.RasterizerDesc.CullMode = state_.culling ? CULL_MODE_BACK : CULL_MODE_NONE;
|
||||
graphics.RasterizerDesc.CullMode = culling ? CULL_MODE_BACK : CULL_MODE_NONE;
|
||||
graphics.RasterizerDesc.FrontCounterClockwise = !state_.normalInverting;
|
||||
graphics.DepthStencilDesc.DepthEnable = state_.depthTest;
|
||||
graphics.DepthStencilDesc.DepthWriteEnable = state_.depthWrite;
|
||||
@@ -564,17 +625,30 @@ Scene::Pipeline* Scene::pipeline(std::uint32_t streams, std::uint32_t textures)
|
||||
|
||||
LayoutElement elements[4];
|
||||
std::uint32_t count = 0;
|
||||
if (sprites) {
|
||||
// One buffer of sprites, each a position and a color, read once per square (instance).
|
||||
elements[0] = LayoutElement{0, 0, 3, VT_FLOAT32, False};
|
||||
elements[1] = LayoutElement{3, 0, 4, VT_UINT8, True};
|
||||
elements[1].RelativeOffset = 12;
|
||||
for (LayoutElement& element : std::span(elements).first(2)) {
|
||||
element.Stride = 16;
|
||||
element.Frequency = INPUT_ELEMENT_FREQUENCY_PER_INSTANCE;
|
||||
element.InstanceDataStepRate = 1;
|
||||
}
|
||||
count = 2;
|
||||
} else {
|
||||
elements[count] = LayoutElement{0, count, 3, VT_FLOAT32, False};
|
||||
++count;
|
||||
if ((streams & hasNormals) != 0) {
|
||||
}
|
||||
if (!sprites && (streams & hasNormals) != 0) {
|
||||
elements[count] = LayoutElement{1, count, 3, VT_FLOAT32, False};
|
||||
++count;
|
||||
}
|
||||
if ((streams & hasTexCoords) != 0) {
|
||||
if (!sprites && (streams & hasTexCoords) != 0) {
|
||||
elements[count] = LayoutElement{2, count, 2, VT_FLOAT32, False};
|
||||
++count;
|
||||
}
|
||||
if ((streams & hasColors) != 0) {
|
||||
if (!sprites && (streams & hasColors) != 0) {
|
||||
elements[count] = LayoutElement{3, count, 4, VT_UINT8, True};
|
||||
++count;
|
||||
}
|
||||
|
||||
+12
-2
@@ -23,6 +23,9 @@ public:
|
||||
// present).
|
||||
bool execute(std::span<const std::byte> commands, int offsetX, bool draw);
|
||||
|
||||
// When the adapter sent the last frame carried out (QueryPerformanceCounter).
|
||||
[[nodiscard]] std::uint64_t lastSentAt() const { return lastSentAt_; }
|
||||
|
||||
private:
|
||||
struct Texture {
|
||||
Diligent::RefCntAutoPtr<Diligent::ITexture> texture;
|
||||
@@ -63,6 +66,7 @@ private:
|
||||
float fogColor[4];
|
||||
float fog[4];
|
||||
float flags[4];
|
||||
float viewport[4];
|
||||
};
|
||||
|
||||
// What a draw needs from the state, as set by the client.
|
||||
@@ -85,12 +89,16 @@ private:
|
||||
void setState(const protocol::SetState& state);
|
||||
void drawTriangles(const protocol::DrawTriangles& draw);
|
||||
void drawArrays(const protocol::Command& command);
|
||||
void drawPointSprites(const protocol::Command& command);
|
||||
|
||||
// Draws indexCount indices from index, with vertex streams (positions required, the others may be null).
|
||||
void draw(Diligent::IBuffer* indices, std::uint32_t indexCount, Diligent::IBuffer* positions,
|
||||
Diligent::IBuffer* normals, Diligent::IBuffer* texCoords, Diligent::IBuffer* colors);
|
||||
Pipeline* pipeline(std::uint32_t streams, std::uint32_t textures);
|
||||
const Shaders& shaders(std::uint32_t streams, std::uint32_t textures);
|
||||
// The pipeline for the current state, with these vertex streams and textures, for triangles or particles.
|
||||
Pipeline* pipeline(std::uint32_t streams, std::uint32_t textures, bool sprites);
|
||||
const Shaders& shaders(std::uint32_t streams, std::uint32_t textures, bool sprites);
|
||||
// Picks the pipeline for the current state and sets it up for a draw: constants, textures. Null if it can't.
|
||||
Pipeline* prepare(std::uint32_t streams, bool sprites);
|
||||
Diligent::ITextureView* textureView(std::uint32_t id);
|
||||
|
||||
Diligent::RefCntAutoPtr<Diligent::IRenderDevice> device_;
|
||||
@@ -108,6 +116,8 @@ private:
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
// environment's ambient light, plus each light's color times max(0, N.L); no specular or emissive light;
|
||||
// - textures: none (the lit color), one (times the lit color), or two (the second blended over the first by its
|
||||
// alpha, then times the lit color), both on the vertex's texture coordinates with their own transforms;
|
||||
// - an alpha test passing pixels whose alpha is greater than the reference, then linear fog by view depth.
|
||||
// - an alpha test passing pixels whose alpha is greater than the reference, then linear fog. Direct3D 9 fogs by
|
||||
// distance from the eye only for projections with +1 as their (3, 4) element; IMVU's (right-handed) have -1, so
|
||||
// it fogs by the depth buffer's value (0 to 1) against the fog's start and end (in scene units, 1 to 50 in
|
||||
// rooms): in effect, no fog. Reproduced as is.
|
||||
//
|
||||
// Particles (vs_sprite) are Direct3D 9 point sprites there: camera-facing squares, viewport height / distance pixels
|
||||
// across (1 to 64), textured over the whole square; here each is drawn as an instance of a 4-vertex strip.
|
||||
//
|
||||
// Compiled for each combination of the vertex streams present (HAS_NORMALS, HAS_TEX_COORDS, HAS_COLORS) and the
|
||||
// number of textures (TEXTURES).
|
||||
@@ -21,6 +27,7 @@ cbuffer Constants
|
||||
float4 g_FogColor; // rgb
|
||||
float4 g_Fog; // x: start, y: end, z: on
|
||||
float4 g_Flags; // x: lighting on, y: alpha test on, z: alpha reference (0-255)
|
||||
float4 g_Viewport; // xy: size in pixels
|
||||
};
|
||||
|
||||
struct VSInput
|
||||
@@ -43,7 +50,6 @@ struct PSInput
|
||||
float4 Color : COLOR0;
|
||||
float2 TexCoord0 : TEXCOORD0;
|
||||
float2 TexCoord1 : TEXCOORD1;
|
||||
float Depth : TEXCOORD2;
|
||||
};
|
||||
|
||||
void vs_main(in VSInput input, out PSInput output)
|
||||
@@ -51,7 +57,6 @@ void vs_main(in VSInput input, out PSInput output)
|
||||
float4 world = mul(float4(input.Position, 1.0), g_World);
|
||||
float4 view = mul(world, g_View);
|
||||
output.Position = mul(view, g_Projection);
|
||||
output.Depth = abs(view.z);
|
||||
|
||||
float4 material = float4(1.0, 1.0, 1.0, 1.0);
|
||||
#if HAS_COLORS
|
||||
@@ -85,6 +90,29 @@ void vs_main(in VSInput input, out PSInput output)
|
||||
output.TexCoord1 = float2(dot(uv, g_TexTransform1[0].xyz), dot(uv, g_TexTransform1[1].xyz));
|
||||
}
|
||||
|
||||
struct SpriteInput
|
||||
{
|
||||
float3 Position : ATTRIB0;
|
||||
float4 Color : ATTRIB3; // bytes B, G, R, A
|
||||
uint Corner : SV_VertexID;
|
||||
};
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
// No normals: lit by the ambient light alone.
|
||||
float4 material = input.Color.zyxw;
|
||||
output.Color = g_Flags.x > 0.5 ? float4(saturate(g_Ambient.rgb * material.rgb), material.a) : material;
|
||||
output.TexCoord0 = float2(corner.x * 0.5 + 0.5, 0.5 - corner.y * 0.5);
|
||||
output.TexCoord1 = output.TexCoord0;
|
||||
}
|
||||
|
||||
#if TEXTURES >= 1
|
||||
Texture2D g_Texture0;
|
||||
SamplerState g_Texture0_sampler;
|
||||
@@ -110,7 +138,7 @@ float4 ps_main(in PSInput input) : SV_Target
|
||||
}
|
||||
if (g_Fog.z > 0.5)
|
||||
{
|
||||
float visible = saturate((g_Fog.y - input.Depth) / max(g_Fog.y - g_Fog.x, 1e-6));
|
||||
float visible = saturate((g_Fog.y - input.Position.z) / max(g_Fog.y - g_Fog.x, 1e-6));
|
||||
color.rgb = lerp(g_FogColor.rgb, color.rgb, visible);
|
||||
}
|
||||
return color;
|
||||
|
||||
Reference in new issue
Block a user