Particles: Direct3D 9's size limit evidently isn't the documented 64 pixels; unclamped (up to 2048) they match the client's renderer. A compare view shows the left half's scene over the right half, both renderers side by side; %TEMP%\t5de-renderer-view.txt switches views and %TEMP%\t5de-renderer-shaders\fixed_function.hlsl replaces the shaders while the client runs. The renderer process runs from a copy, so the program can be replaced meanwhile. The adapter recognizes only the textures and buffers it made (others reach the client renderer unchanged) and logs the others and render targets. Co-Authored-By: Claude Opus 5.5 <[email protected]>
717 lines
29 KiB
C++
717 lines
29 KiB
C++
#include "scene.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
#include "fixed_function_hlsl.hpp"
|
|
#include "log.hpp"
|
|
|
|
namespace t5de {
|
|
|
|
using namespace Diligent;
|
|
using protocol::Op;
|
|
|
|
namespace {
|
|
|
|
void set_identity(float (&m)[16])
|
|
{
|
|
std::memset(m, 0, sizeof(m));
|
|
m[0] = m[5] = m[10] = m[15] = 1;
|
|
}
|
|
|
|
// An affine transform (3x3 row-major, then translation) as a 4x4 matrix for row vectors.
|
|
void set_transform(float (&m)[16], const float (&t)[12])
|
|
{
|
|
for (int row = 0; row < 3; ++row) {
|
|
m[row * 4 + 0] = t[row * 3 + 0];
|
|
m[row * 4 + 1] = t[row * 3 + 1];
|
|
m[row * 4 + 2] = t[row * 3 + 2];
|
|
m[row * 4 + 3] = 0;
|
|
}
|
|
m[12] = t[9];
|
|
m[13] = t[10];
|
|
m[14] = t[11];
|
|
m[15] = 1;
|
|
}
|
|
|
|
// u' = u * m[0] + v * m[1] + m[4], v' = u * m[2] + v * m[3] + m[5], as two rows dotted with (u, v, 1).
|
|
void set_texture_transform(float (&rows)[8], const float (&m)[6])
|
|
{
|
|
const float values[8] = {m[0], m[1], m[4], 0, m[2], m[3], m[5], 0};
|
|
std::memcpy(rows, values, sizeof(rows));
|
|
}
|
|
|
|
void set_color(float (&rgba)[4], std::uint32_t color)
|
|
{
|
|
rgba[0] = static_cast<float>((color >> 16) & 0xff) / 255.0f;
|
|
rgba[1] = static_cast<float>((color >> 8) & 0xff) / 255.0f;
|
|
rgba[2] = static_cast<float>(color & 0xff) / 255.0f;
|
|
rgba[3] = static_cast<float>(color >> 24) / 255.0f;
|
|
}
|
|
|
|
constexpr std::uint32_t hasNormals = 1;
|
|
constexpr std::uint32_t hasTexCoords = 2;
|
|
constexpr std::uint32_t hasColors = 4;
|
|
|
|
} // namespace
|
|
|
|
Scene::Scene(IRenderDevice* device, IDeviceContext* context, ISwapChain* swapChain)
|
|
: device_(device), context_(context), swapChain_(swapChain), shaderSource_(fixedFunctionHlsl)
|
|
{
|
|
BufferDesc constants;
|
|
constants.Name = "Constants";
|
|
constants.Size = sizeof(Constants);
|
|
constants.Usage = USAGE_DYNAMIC;
|
|
constants.BindFlags = BIND_UNIFORM_BUFFER;
|
|
constants.CPUAccessFlags = CPU_ACCESS_WRITE;
|
|
device_->CreateBuffer(constants, nullptr, &constantBuffer_);
|
|
|
|
// IMVU's textures: bilinear, no mipmaps, wrapped or clamped per texture.
|
|
for (int wrap = 0; wrap < 4; ++wrap) {
|
|
SamplerDesc sampler;
|
|
sampler.MinFilter = FILTER_TYPE_LINEAR;
|
|
sampler.MagFilter = FILTER_TYPE_LINEAR;
|
|
sampler.MipFilter = FILTER_TYPE_POINT;
|
|
sampler.AddressU = (wrap & 1) != 0 ? TEXTURE_ADDRESS_WRAP : TEXTURE_ADDRESS_CLAMP;
|
|
sampler.AddressV = (wrap & 2) != 0 ? TEXTURE_ADDRESS_WRAP : TEXTURE_ADDRESS_CLAMP;
|
|
device_->CreateSampler(sampler, &samplers_[wrap]);
|
|
}
|
|
|
|
set_identity(constants_.world);
|
|
set_identity(constants_.view);
|
|
set_identity(constants_.projection);
|
|
const float identity[6] = {1, 0, 0, 1, 0, 0};
|
|
set_texture_transform(constants_.texTransform0, identity);
|
|
set_texture_transform(constants_.texTransform1, identity);
|
|
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;
|
|
draw_ = draw;
|
|
bool drew = false;
|
|
protocol::Reader reader(commands);
|
|
while (const auto command = reader.next()) {
|
|
switch (command->op()) {
|
|
case Op::BeginFrame:
|
|
if (const auto begin = command->body<protocol::BeginFrame>()) {
|
|
onscreen_ = begin->offscreen == 0 && draw_;
|
|
if (onscreen_) {
|
|
ITextureView* target = swapChain_->GetCurrentBackBufferRTV();
|
|
context_->SetRenderTargets(1, &target, swapChain_->GetDepthBufferDSV(),
|
|
RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
|
}
|
|
}
|
|
break;
|
|
case Op::EndFrame:
|
|
if (const auto end = command->body<protocol::EndFrame>()) {
|
|
lastSentAt_ = end->sentAt;
|
|
}
|
|
drew = drew || onscreen_;
|
|
onscreen_ = false;
|
|
break;
|
|
case Op::Clear:
|
|
if (const auto clear = command->body<protocol::Clear>(); clear && onscreen_) {
|
|
if ((clear->flags & 1) != 0) {
|
|
float color[4];
|
|
set_color(color, clear->color);
|
|
context_->ClearRenderTarget(swapChain_->GetCurrentBackBufferRTV(), color,
|
|
RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
|
}
|
|
if ((clear->flags & 2) != 0) {
|
|
context_->ClearDepthStencil(swapChain_->GetDepthBufferDSV(), CLEAR_DEPTH_FLAG, 1.0f, 0,
|
|
RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
|
}
|
|
}
|
|
break;
|
|
case Op::SetProjection:
|
|
if (const auto projection = command->body<protocol::SetProjection>()) {
|
|
setProjection(*projection);
|
|
}
|
|
break;
|
|
case Op::SetModel:
|
|
if (const auto model = command->body<protocol::SetTransform>()) {
|
|
set_transform(constants_.world, model->m);
|
|
}
|
|
break;
|
|
case Op::SetView:
|
|
if (const auto view = command->body<protocol::SetTransform>()) {
|
|
set_transform(constants_.view, view->m);
|
|
}
|
|
break;
|
|
case Op::SetTextures:
|
|
if (const auto textures = command->body<protocol::SetTextures>()) {
|
|
state_.texture0 = textures->texture0;
|
|
state_.texture1 = textures->texture1;
|
|
}
|
|
break;
|
|
case Op::SetState:
|
|
if (const auto state = command->body<protocol::SetState>()) {
|
|
setState(*state);
|
|
}
|
|
break;
|
|
case Op::SetLight:
|
|
if (const auto light = command->body<protocol::SetLight>(); light && light->index < 8) {
|
|
set_color(constants_.lightColor[light->index], light->color);
|
|
constants_.lightColor[light->index][3] = light->enabled != 0 ? 1.0f : 0.0f;
|
|
std::memcpy(constants_.lightVector[light->index], light->vector, sizeof(light->vector));
|
|
constants_.lightVector[light->index][3] = light->directional != 0 ? 1.0f : 0.0f;
|
|
}
|
|
break;
|
|
case Op::SetFog:
|
|
if (const auto fog = command->body<protocol::SetFog>()) {
|
|
constants_.fog[0] = fog->start;
|
|
constants_.fog[1] = fog->end;
|
|
set_color(constants_.fogColor, fog->color);
|
|
}
|
|
break;
|
|
case Op::SetTextureTransform:
|
|
if (const auto transform = command->body<protocol::SetTextureTransform>(); transform && transform->stage < 2) {
|
|
set_texture_transform(transform->stage == 0 ? constants_.texTransform0 : constants_.texTransform1,
|
|
transform->m);
|
|
}
|
|
break;
|
|
case Op::CreateTexture:
|
|
createTexture(*command);
|
|
break;
|
|
case Op::UpdateTexture:
|
|
updateTexture(*command);
|
|
break;
|
|
case Op::DestroyTexture:
|
|
if (const auto destroy = command->body<protocol::DestroyTexture>()) {
|
|
textures_.erase(destroy->id);
|
|
}
|
|
break;
|
|
case Op::CreateBuffer:
|
|
createBuffer(*command);
|
|
break;
|
|
case Op::UpdateBuffer:
|
|
updateBuffer(*command);
|
|
break;
|
|
case Op::DestroyBuffer:
|
|
if (const auto destroy = command->body<protocol::DestroyBuffer>()) {
|
|
buffers_.erase(destroy->id);
|
|
}
|
|
break;
|
|
case Op::DrawTriangles:
|
|
if (const auto triangles = command->body<protocol::DrawTriangles>(); triangles && onscreen_) {
|
|
drawTriangles(*triangles);
|
|
}
|
|
break;
|
|
case Op::DrawArrays:
|
|
if (onscreen_) {
|
|
drawArrays(*command);
|
|
}
|
|
break;
|
|
case Op::DrawPointSprites:
|
|
if (onscreen_) {
|
|
drawPointSprites(*command);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return drew;
|
|
}
|
|
|
|
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;
|
|
}
|
|
// The client's projection covers its whole 3D view; the window shows part of it, from offsetX.
|
|
const SwapChainDesc& window = swapChain_->GetDesc();
|
|
Viewport viewport;
|
|
viewport.TopLeftX = static_cast<float>(-offsetX_);
|
|
viewport.TopLeftY = 0;
|
|
viewport.Width = static_cast<float>(projection.width);
|
|
viewport.Height = static_cast<float>(projection.height);
|
|
viewport.MinDepth = 0;
|
|
viewport.MaxDepth = 1;
|
|
context_->SetViewports(1, &viewport, window.Width, window.Height);
|
|
}
|
|
|
|
void Scene::setState(const protocol::SetState& state)
|
|
{
|
|
const bool on = state.value != 0;
|
|
switch (state.state) {
|
|
case protocol::State::DepthTest:
|
|
state_.depthTest = on;
|
|
break;
|
|
case protocol::State::DepthWrite:
|
|
state_.depthWrite = on;
|
|
break;
|
|
case protocol::State::Blending:
|
|
state_.blending = on;
|
|
break;
|
|
case protocol::State::BlendMode:
|
|
if (state.value >= 1 && state.value <= 4) {
|
|
state_.blendMode = state.value;
|
|
}
|
|
break;
|
|
case protocol::State::AlphaTest:
|
|
constants_.flags[1] = on ? 1.0f : 0.0f;
|
|
break;
|
|
case protocol::State::AlphaReference:
|
|
constants_.flags[2] = static_cast<float>(state.value);
|
|
break;
|
|
case protocol::State::Culling:
|
|
state_.culling = on;
|
|
break;
|
|
case protocol::State::NormalInverting:
|
|
state_.normalInverting = on;
|
|
break;
|
|
case protocol::State::Lighting:
|
|
constants_.flags[0] = on ? 1.0f : 0.0f;
|
|
break;
|
|
case protocol::State::Fog:
|
|
constants_.fog[2] = on ? 1.0f : 0.0f;
|
|
break;
|
|
case protocol::State::Ambient:
|
|
set_color(constants_.ambient, state.value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Scene::createTexture(const protocol::Command& command)
|
|
{
|
|
const auto create = command.body<protocol::CreateTexture>();
|
|
const auto pixels = command.payload<protocol::CreateTexture>();
|
|
if (!create || create->width == 0 || create->height == 0
|
|
|| pixels.size() < std::size_t{create->width} * create->height * 4) {
|
|
return;
|
|
}
|
|
TextureDesc desc;
|
|
desc.Name = "Client texture";
|
|
desc.Type = RESOURCE_DIM_TEX_2D;
|
|
desc.Width = create->width;
|
|
desc.Height = create->height;
|
|
desc.MipLevels = 1;
|
|
desc.Format = TEX_FORMAT_BGRA8_UNORM; // 0xAARRGGBB in memory
|
|
desc.BindFlags = BIND_SHADER_RESOURCE;
|
|
desc.Usage = USAGE_DEFAULT;
|
|
TextureSubResData level;
|
|
level.pData = pixels.data();
|
|
level.Stride = std::uint64_t{create->width} * 4;
|
|
TextureData data;
|
|
data.pSubResources = &level;
|
|
data.NumSubresources = 1;
|
|
Texture texture{.width = create->width, .height = create->height, .flags = create->flags};
|
|
device_->CreateTexture(desc, &data, &texture.texture);
|
|
if (!texture.texture) {
|
|
return;
|
|
}
|
|
texture.texture->GetDefaultView(TEXTURE_VIEW_SHADER_RESOURCE)
|
|
->SetSampler(samplers_[((create->flags & 2) != 0 ? 1 : 0) | ((create->flags & 4) != 0 ? 2 : 0)]);
|
|
textures_[create->id] = std::move(texture);
|
|
}
|
|
|
|
void Scene::updateTexture(const protocol::Command& command)
|
|
{
|
|
const auto update = command.body<protocol::UpdateTexture>();
|
|
const auto pixels = command.payload<protocol::UpdateTexture>();
|
|
if (!update || pixels.size() < std::size_t{update->width} * update->height * 4) {
|
|
return;
|
|
}
|
|
const auto found = textures_.find(update->id);
|
|
if (found == textures_.end()) {
|
|
return;
|
|
}
|
|
Texture& texture = found->second;
|
|
if (texture.width != update->width || texture.height != update->height) {
|
|
// A different size: make it again, keeping its sampling.
|
|
protocol::Writer writer;
|
|
std::memcpy(writer.add(Op::CreateTexture,
|
|
protocol::CreateTexture{update->id, update->width, update->height, texture.flags},
|
|
pixels.size()),
|
|
pixels.data(), pixels.size());
|
|
protocol::Reader reader(writer.bytes());
|
|
createTexture(*reader.next());
|
|
return;
|
|
}
|
|
Box box;
|
|
box.MaxX = update->width;
|
|
box.MaxY = update->height;
|
|
TextureSubResData level;
|
|
level.pData = pixels.data();
|
|
level.Stride = std::uint64_t{update->width} * 4;
|
|
context_->UpdateTexture(texture.texture, 0, 0, box, level, RESOURCE_STATE_TRANSITION_MODE_TRANSITION,
|
|
RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
|
}
|
|
|
|
void Scene::createBuffer(const protocol::Command& command)
|
|
{
|
|
const auto create = command.body<protocol::CreateBuffer>();
|
|
if (!create || create->count == 0 || create->elementSize == 0) {
|
|
return;
|
|
}
|
|
const std::size_t size = std::size_t{create->elementSize} * create->count;
|
|
const auto contents = command.payload<protocol::CreateBuffer>();
|
|
const bool filled = contents.size() >= size;
|
|
BufferDesc desc;
|
|
desc.Name = create->kind == protocol::BufferKind::Index ? "Client indices" : "Client vertices";
|
|
desc.Size = size;
|
|
desc.BindFlags = create->kind == protocol::BufferKind::Index ? BIND_INDEX_BUFFER : BIND_VERTEX_BUFFER;
|
|
desc.Usage = filled ? USAGE_IMMUTABLE : USAGE_DEFAULT; // dynamic buffers are filled by updates
|
|
BufferData data;
|
|
data.pData = contents.data();
|
|
data.DataSize = size;
|
|
Buffer buffer{.kind = create->kind, .elementSize = create->elementSize, .count = create->count};
|
|
device_->CreateBuffer(desc, filled ? &data : nullptr, &buffer.buffer);
|
|
if (buffer.buffer) {
|
|
buffers_[create->id] = std::move(buffer);
|
|
}
|
|
}
|
|
|
|
void Scene::updateBuffer(const protocol::Command& command)
|
|
{
|
|
const auto update = command.body<protocol::UpdateBuffer>();
|
|
const auto contents = command.payload<protocol::UpdateBuffer>();
|
|
if (!update) {
|
|
return;
|
|
}
|
|
const auto found = buffers_.find(update->id);
|
|
const std::size_t size = std::size_t{update->elementSize} * update->count;
|
|
if (found == buffers_.end() || contents.size() < size || found->second.elementSize != update->elementSize
|
|
|| found->second.count != update->count || found->second.buffer->GetDesc().Usage != USAGE_DEFAULT) {
|
|
return;
|
|
}
|
|
context_->UpdateBuffer(found->second.buffer, 0, size, contents.data(), RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
|
}
|
|
|
|
ITextureView* Scene::textureView(std::uint32_t id)
|
|
{
|
|
const auto found = textures_.find(id);
|
|
return found == textures_.end() ? nullptr : found->second.texture->GetDefaultView(TEXTURE_VIEW_SHADER_RESOURCE);
|
|
}
|
|
|
|
void Scene::drawTriangles(const protocol::DrawTriangles& draw)
|
|
{
|
|
const auto buffer = [this](std::uint32_t id, protocol::BufferKind kind, std::uint32_t elementSize) -> Buffer* {
|
|
const auto found = buffers_.find(id);
|
|
if (id == 0 || found == buffers_.end() || found->second.kind != kind || found->second.elementSize != elementSize) {
|
|
return nullptr;
|
|
}
|
|
return &found->second;
|
|
};
|
|
Buffer* indices = buffer(draw.indices, protocol::BufferKind::Index, 2);
|
|
Buffer* positions = buffer(draw.positions, protocol::BufferKind::Vertex, 12);
|
|
if (indices == nullptr || positions == nullptr) {
|
|
return;
|
|
}
|
|
// Optional streams count only if they cover every vertex.
|
|
const auto optional = [&](std::uint32_t id, std::uint32_t elementSize) -> IBuffer* {
|
|
Buffer* found = buffer(id, protocol::BufferKind::Vertex, elementSize);
|
|
return found != nullptr && found->count >= positions->count ? found->buffer.RawPtr() : nullptr;
|
|
};
|
|
this->draw(indices->buffer, indices->count, positions->buffer, optional(draw.normals, 12),
|
|
optional(draw.texCoords, 8), optional(draw.colors, 4));
|
|
}
|
|
|
|
void Scene::drawArrays(const protocol::Command& command)
|
|
{
|
|
const auto arrays = command.body<protocol::DrawArrays>();
|
|
const auto payload = command.payload<protocol::DrawArrays>();
|
|
if (!arrays || arrays->vertexCount == 0 || arrays->indexCount == 0) {
|
|
return;
|
|
}
|
|
const std::size_t indexBytes = protocol::padded(std::size_t{arrays->indexCount} * 2);
|
|
const std::size_t vertices = arrays->vertexCount;
|
|
if (payload.size() < indexBytes + vertices * (12 + (arrays->hasNormals ? 12 : 0) + (arrays->hasTexCoords ? 8 : 0) +
|
|
(arrays->hasColors ? 4 : 0))) {
|
|
return;
|
|
}
|
|
const auto make = [this](const std::byte* data, std::size_t size, BIND_FLAGS bind) {
|
|
BufferDesc desc;
|
|
desc.Name = "Client arrays";
|
|
desc.Size = size;
|
|
desc.BindFlags = bind;
|
|
desc.Usage = USAGE_IMMUTABLE;
|
|
BufferData contents;
|
|
contents.pData = data;
|
|
contents.DataSize = size;
|
|
RefCntAutoPtr<IBuffer> buffer;
|
|
device_->CreateBuffer(desc, &contents, &buffer);
|
|
return buffer;
|
|
};
|
|
const std::byte* next = payload.data();
|
|
const auto indices = make(next, std::size_t{arrays->indexCount} * 2, BIND_INDEX_BUFFER);
|
|
next += indexBytes;
|
|
const auto positions = make(next, vertices * 12, BIND_VERTEX_BUFFER);
|
|
next += vertices * 12;
|
|
RefCntAutoPtr<IBuffer> normals, texCoords, colors;
|
|
if (arrays->hasNormals) {
|
|
normals = make(next, vertices * 12, BIND_VERTEX_BUFFER);
|
|
next += vertices * 12;
|
|
}
|
|
if (arrays->hasTexCoords) {
|
|
texCoords = make(next, vertices * 8, BIND_VERTEX_BUFFER);
|
|
next += vertices * 8;
|
|
}
|
|
if (arrays->hasColors) {
|
|
colors = make(next, vertices * 4, BIND_VERTEX_BUFFER);
|
|
}
|
|
draw(indices, arrays->indexCount, positions, normals, texCoords, colors);
|
|
}
|
|
|
|
void Scene::draw(IBuffer* indices, std::uint32_t indexCount, IBuffer* positions, IBuffer* normals, IBuffer* texCoords,
|
|
IBuffer* colors)
|
|
{
|
|
if (indices == nullptr || positions == nullptr || indexCount == 0) {
|
|
return;
|
|
}
|
|
const std::uint32_t streams =
|
|
(normals != nullptr ? hasNormals : 0) | (texCoords != nullptr ? hasTexCoords : 0) | (colors != nullptr ? hasColors : 0);
|
|
if (prepare(streams, false) == nullptr) {
|
|
return;
|
|
}
|
|
|
|
IBuffer* vertexBuffers[4];
|
|
std::uint32_t count = 0;
|
|
for (IBuffer* stream : {positions, normals, texCoords, colors}) {
|
|
if (stream != nullptr) {
|
|
vertexBuffers[count++] = stream;
|
|
}
|
|
}
|
|
context_->SetVertexBuffers(0, count, vertexBuffers, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION,
|
|
SET_VERTEX_BUFFERS_FLAG_RESET);
|
|
context_->SetIndexBuffer(indices, 0, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
|
DrawIndexedAttribs attributes;
|
|
attributes.NumIndices = indexCount;
|
|
attributes.IndexType = VT_UINT16;
|
|
context_->DrawIndexed(attributes);
|
|
}
|
|
|
|
Scene::Pipeline* Scene::prepare(std::uint32_t streams, bool sprites)
|
|
{
|
|
// 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]);
|
|
// The first sprite: where it is, seen from the camera, and how big it's drawn.
|
|
float point[4] = {0, 0, 0, 1};
|
|
std::uint32_t color = 0;
|
|
std::memcpy(point, payload.data(), 12);
|
|
std::memcpy(&color, payload.data() + 12, 4);
|
|
const auto transform = [](const float (&p)[4], const float (&m)[16]) {
|
|
std::array<float, 4> out{};
|
|
for (int column = 0; column < 4; ++column) {
|
|
out[column] = p[0] * m[column] + p[1] * m[4 + column] + p[2] * m[8 + column] + p[3] * m[12 + column];
|
|
}
|
|
return out;
|
|
};
|
|
const auto world = transform(point, constants_.world);
|
|
const float worldPoint[4] = {world[0], world[1], world[2], world[3]};
|
|
const auto view = transform(worldPoint, constants_.view);
|
|
const float distance = std::sqrt(view[0] * view[0] + view[1] * view[1] + view[2] * view[2]);
|
|
log::info(" first sprite at ({:.2f} {:.2f} {:.2f}), color {:08x}, from the camera ({:.2f} {:.2f} {:.2f}): "
|
|
"distance {:.2f}, {:.1f} pixels across (viewport height {})",
|
|
point[0], point[1], point[2], color, view[0], view[1], view[2], distance,
|
|
std::clamp(constants_.viewport[1] / std::max(distance, 1e-4f), 1.0f, 2048.0f), constants_.viewport[1]);
|
|
}
|
|
IBuffer* buffers[] = {buffer};
|
|
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;
|
|
}
|
|
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" + shaderSource_;
|
|
Shaders created;
|
|
ShaderCreateInfo info;
|
|
info.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL;
|
|
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 = 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)";
|
|
info.EntryPoint = "ps_main";
|
|
device_->CreateShader(info, &created.pixel);
|
|
return shaders_[key] = std::move(created);
|
|
}
|
|
|
|
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) | (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, sprites);
|
|
if (!compiled.vertex || !compiled.pixel) {
|
|
return nullptr;
|
|
}
|
|
|
|
GraphicsPipelineStateCreateInfo info;
|
|
info.PSODesc.Name = "Fixed function";
|
|
info.PSODesc.PipelineType = PIPELINE_TYPE_GRAPHICS;
|
|
const SwapChainDesc& window = swapChain_->GetDesc();
|
|
GraphicsPipelineDesc& graphics = info.GraphicsPipeline;
|
|
graphics.NumRenderTargets = 1;
|
|
graphics.RTVFormats[0] = window.ColorBufferFormat;
|
|
graphics.DSVFormat = window.DepthBufferFormat;
|
|
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 = culling ? CULL_MODE_BACK : CULL_MODE_NONE;
|
|
graphics.RasterizerDesc.FrontCounterClockwise = !state_.normalInverting;
|
|
graphics.DepthStencilDesc.DepthEnable = state_.depthTest;
|
|
graphics.DepthStencilDesc.DepthWriteEnable = state_.depthWrite;
|
|
graphics.DepthStencilDesc.DepthFunc = COMPARISON_FUNC_LESS;
|
|
RenderTargetBlendDesc& blending = graphics.BlendDesc.RenderTargets[0];
|
|
blending.BlendEnable = blend != 0;
|
|
if (blend != 0) {
|
|
const BLEND_FACTOR source = blend == 1 || blend == 4 ? BLEND_FACTOR_SRC_ALPHA : BLEND_FACTOR_ONE;
|
|
const BLEND_FACTOR destination = blend == 2 || blend == 4 ? BLEND_FACTOR_ONE : BLEND_FACTOR_INV_SRC_ALPHA;
|
|
blending.SrcBlend = blending.SrcBlendAlpha = source;
|
|
blending.DestBlend = blending.DestBlendAlpha = destination;
|
|
blending.BlendOp = blending.BlendOpAlpha = BLEND_OPERATION_ADD;
|
|
}
|
|
|
|
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 (!sprites && (streams & hasNormals) != 0) {
|
|
elements[count] = LayoutElement{1, count, 3, VT_FLOAT32, False};
|
|
++count;
|
|
}
|
|
if (!sprites && (streams & hasTexCoords) != 0) {
|
|
elements[count] = LayoutElement{2, count, 2, VT_FLOAT32, False};
|
|
++count;
|
|
}
|
|
if (!sprites && (streams & hasColors) != 0) {
|
|
elements[count] = LayoutElement{3, count, 4, VT_UINT8, True};
|
|
++count;
|
|
}
|
|
graphics.InputLayout.LayoutElements = elements;
|
|
graphics.InputLayout.NumElements = count;
|
|
|
|
info.pVS = compiled.vertex;
|
|
info.pPS = compiled.pixel;
|
|
const ShaderResourceVariableDesc variables[] = {
|
|
{SHADER_TYPE_PIXEL, "g_Texture0", SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC},
|
|
{SHADER_TYPE_PIXEL, "g_Texture1", SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC},
|
|
};
|
|
info.PSODesc.ResourceLayout.DefaultVariableType = SHADER_RESOURCE_VARIABLE_TYPE_STATIC;
|
|
info.PSODesc.ResourceLayout.Variables = variables;
|
|
info.PSODesc.ResourceLayout.NumVariables = textures;
|
|
device_->CreateGraphicsPipelineState(info, &entry.state);
|
|
if (!entry.state) {
|
|
return nullptr;
|
|
}
|
|
for (const SHADER_TYPE stage : {SHADER_TYPE_VERTEX, SHADER_TYPE_PIXEL}) {
|
|
if (IShaderResourceVariable* constants = entry.state->GetStaticVariableByName(stage, "Constants")) {
|
|
constants->Set(constantBuffer_);
|
|
}
|
|
}
|
|
entry.state->CreateShaderResourceBinding(&entry.bindings, true);
|
|
if (textures >= 1) {
|
|
entry.texture0 = entry.bindings->GetVariableByName(SHADER_TYPE_PIXEL, "g_Texture0");
|
|
}
|
|
if (textures >= 2) {
|
|
entry.texture1 = entry.bindings->GetVariableByName(SHADER_TYPE_PIXEL, "g_Texture1");
|
|
}
|
|
return &entry;
|
|
}
|
|
|
|
} // namespace t5de
|