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

+1 -1
View File
@@ -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)
+30
View File
@@ -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
+18
View File
@@ -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
View File
@@ -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;
}
+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;
}
+12 -2
View File
@@ -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;
};
+32 -4
View File
@@ -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;