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:
dhkatzandClaude Opus 5.5 committed 2026-10-28 06:11:51 -07:00
1 parent 42fc55cb91
commit a04dba18c1
12 files changed
+293 -90

No files matched your search

+112 -38
View File
@@ -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;
elements[count] = LayoutElement{0, count, 3, VT_FLOAT32, False};
++count;
if ((streams & hasNormals) != 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 (!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;
}