Draw the client's scene in the renderer process
The adapter records everything the client draws (state, transforms, lights, fog, textures and buffers) as a command stream, sent in chunks through the shared memory channel, while IMVU's Direct3D renderer still draws the whole view. The renderer process draws it with Diligent, with shaders reproducing IMVU's Direct3D 9 fixed-function setup, over the right half of the 3D view to compare. It skips to the newest frame when it falls behind, and logs its errors and frame times. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
5c35c8b63a
commit
42fc55cb91
23 files changed
+2121
-472
No files matched your search
+14
-1
@@ -20,8 +20,21 @@ FetchContent_Declare(DiligentCore
|
||||
SYSTEM)
|
||||
FetchContent_MakeAvailable(DiligentCore)
|
||||
|
||||
add_executable(t5de_renderer WIN32 main.cpp)
|
||||
# Shader sources, embedded in the program (Diligent compiles them at run time): each shaders/<name>.hlsl becomes
|
||||
# <name>_hlsl.hpp, defining <camelCaseName>Hlsl.
|
||||
function(t5de_embed_shader file name)
|
||||
set(T5DE_SHADER_FILE ${file})
|
||||
set(T5DE_SHADER_NAME ${name})
|
||||
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/shaders/${file} T5DE_SHADER_SOURCE)
|
||||
get_filename_component(stem ${file} NAME_WE)
|
||||
configure_file(shaders/embed.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/generated/${stem}_hlsl.hpp @ONLY)
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS shaders/${file})
|
||||
endfunction()
|
||||
t5de_embed_shader(fixed_function.hlsl fixedFunctionHlsl)
|
||||
|
||||
add_executable(t5de_renderer WIN32 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)
|
||||
|
||||
install(TARGETS t5de_renderer RUNTIME DESTINATION .)
|
||||
+132
-25
@@ -1,31 +1,63 @@
|
||||
// t5de-renderer.exe: the renderer process. Started by the adapter (t5deRenderEngine.dll) for each of the client's
|
||||
// 3D views; draws into a child window it places in the client's window, frame by frame as the client draws.
|
||||
//
|
||||
// For now a spike proving the architecture: a small box in the 3D view's corner, its color changing with each frame
|
||||
// the client draws, drawn with Diligent (Direct3D 11) from this 64-bit process.
|
||||
// 3D views: receives the client's drawing as commands through a shared memory channel, and draws it with Diligent
|
||||
// into a child window it places in the client's window (over the right half, for now, to compare with the client's
|
||||
// own renderer on the left).
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <share.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "EngineFactoryD3D11.h"
|
||||
#include "RefCntAutoPtr.hpp"
|
||||
#include "scene.hpp"
|
||||
#include "t5de/protocol/channel.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace Diligent;
|
||||
using t5de::protocol::Channel;
|
||||
using t5de::protocol::View;
|
||||
|
||||
// The box: where in the client's 3D view, and how big.
|
||||
constexpr int boxX = 16;
|
||||
constexpr int boxY = 16;
|
||||
constexpr int boxWidth = 240;
|
||||
constexpr int boxHeight = 80;
|
||||
// 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)...));
|
||||
}
|
||||
|
||||
// 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 : "",
|
||||
function != nullptr ? ": " : "", message != nullptr ? message : "");
|
||||
}
|
||||
|
||||
struct Arguments {
|
||||
std::wstring channel;
|
||||
@@ -67,7 +99,18 @@ HWND create_child_window(HWND parent)
|
||||
.lpszClassName = L"T5DERenderer"};
|
||||
RegisterClassExW(&windowClass);
|
||||
return CreateWindowExW(0, windowClass.lpszClassName, L"", WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_CLIPSIBLINGS,
|
||||
boxX, boxY, boxWidth, boxHeight, parent, nullptr, windowClass.hInstance, nullptr);
|
||||
0, 0, 1, 1, parent, nullptr, windowClass.hInstance, nullptr);
|
||||
}
|
||||
|
||||
// Where the child window goes in the client's 3D view.
|
||||
RECT placement(HWND parent, View view)
|
||||
{
|
||||
RECT client{};
|
||||
GetClientRect(parent, &client);
|
||||
if (view == View::Split) {
|
||||
client.left = client.right / 2;
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -80,37 +123,65 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||
}
|
||||
HANDLE mapping = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, arguments.channel.c_str());
|
||||
auto* channel = mapping == nullptr ? nullptr
|
||||
: static_cast<t5de::protocol::Channel*>(MapViewOfFile(
|
||||
mapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(t5de::protocol::Channel)));
|
||||
HANDLE frameEvent = OpenEventW(SYNCHRONIZE, FALSE, (arguments.channel + t5de::protocol::frameEventSuffix).c_str());
|
||||
: static_cast<Channel*>(MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, 0));
|
||||
HANDLE ready = OpenEventW(SYNCHRONIZE, FALSE, (arguments.channel + t5de::protocol::readyEventSuffix).c_str());
|
||||
HANDLE consumed =
|
||||
OpenEventW(EVENT_MODIFY_STATE, FALSE, (arguments.channel + t5de::protocol::consumedEventSuffix).c_str());
|
||||
HANDLE parent = OpenProcess(SYNCHRONIZE, FALSE, arguments.parent);
|
||||
if (channel == nullptr || frameEvent == nullptr || parent == nullptr || channel->version != t5de::protocol::version) {
|
||||
if (channel == nullptr || ready == nullptr || consumed == nullptr || parent == nullptr
|
||||
|| channel->version != t5de::protocol::version) {
|
||||
return 3;
|
||||
}
|
||||
const auto* data = reinterpret_cast<const std::byte*>(channel + 1);
|
||||
|
||||
HWND window = create_child_window(reinterpret_cast<HWND>(static_cast<std::uintptr_t>(channel->window)));
|
||||
const HWND clientWindow = reinterpret_cast<HWND>(static_cast<std::uintptr_t>(channel->window));
|
||||
const HWND window = create_child_window(clientWindow);
|
||||
if (window == nullptr) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
log("started for window {:#x}, view {}", channel->window, static_cast<int>(channel->view));
|
||||
IEngineFactoryD3D11* factory = GetEngineFactoryD3D11();
|
||||
factory->SetMessageCallback(diligent_message);
|
||||
RefCntAutoPtr<IRenderDevice> device;
|
||||
RefCntAutoPtr<IDeviceContext> context;
|
||||
EngineD3D11CreateInfo engineInfo;
|
||||
factory->CreateDeviceAndContextsD3D11(engineInfo, &device, &context);
|
||||
if (!device) {
|
||||
log("can't create a Direct3D 11 device");
|
||||
return 5;
|
||||
}
|
||||
RefCntAutoPtr<ISwapChain> swapChain;
|
||||
SwapChainDesc swapChainDesc;
|
||||
swapChainDesc.ColorBufferFormat = TEX_FORMAT_RGBA8_UNORM; // no sRGB conversion, like the client's renderer
|
||||
swapChainDesc.DepthBufferFormat = TEX_FORMAT_D32_FLOAT;
|
||||
factory->CreateSwapChainD3D11(device, context, swapChainDesc, FullScreenModeDesc{}, Win32NativeWindow{window},
|
||||
&swapChain);
|
||||
if (!swapChain) {
|
||||
log("can't create a swap chain");
|
||||
return 6;
|
||||
}
|
||||
t5de::Scene scene(device, context, swapChain);
|
||||
|
||||
// Takes the chunk waiting in the channel (after "ready"), and lets the adapter send the next: appends it to
|
||||
// frame, and returns whether it ends a frame.
|
||||
std::vector<std::byte> frame;
|
||||
const auto take_chunk = [&] {
|
||||
const std::uint32_t size = channel->chunkSize <= channel->capacity ? channel->chunkSize : 0;
|
||||
const bool endsFrame = (channel->chunkFlags & t5de::protocol::chunkEndsFrame) != 0;
|
||||
frame.insert(frame.end(), data, data + size);
|
||||
SetEvent(consumed);
|
||||
return endsFrame;
|
||||
};
|
||||
|
||||
LARGE_INTEGER frequency;
|
||||
QueryPerformanceFrequency(&frequency);
|
||||
double drawTime = 0;
|
||||
int drawn = 0;
|
||||
int skipped = 0;
|
||||
RECT placed{};
|
||||
for (;;) {
|
||||
const HANDLE handles[] = {frameEvent, parent};
|
||||
const HANDLE handles[] = {ready, parent};
|
||||
const DWORD result = MsgWaitForMultipleObjects(2, handles, FALSE, 1000, QS_ALLINPUT);
|
||||
MSG message;
|
||||
while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE)) {
|
||||
@@ -119,19 +190,55 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||
if (result == WAIT_OBJECT_0 + 1 || channel->closing != 0) {
|
||||
break; // the client exited, or the engine is gone
|
||||
}
|
||||
if (result != WAIT_OBJECT_0) {
|
||||
if (result != WAIT_OBJECT_0 || !take_chunk()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// T5DE gold, pulsing with the client's frames.
|
||||
const float pulse = 0.5f + 0.5f * std::sin(static_cast<float>(channel->frames) * 0.1f);
|
||||
const float color[] = {1.0f, 0.82f * pulse + 0.1f, 0.0f, 1.0f};
|
||||
ITextureView* backBuffer = swapChain->GetCurrentBackBufferRTV();
|
||||
context->SetRenderTargets(1, &backBuffer, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||
context->ClearRenderTarget(backBuffer, color, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||
swapChain->Present(0);
|
||||
// If the client has sent more frames meanwhile (drawing fell behind), catch up: carry out the older ones'
|
||||
// changes without drawing them, and draw only the newest. (An incomplete frame stays for the next round.)
|
||||
std::size_t complete = frame.size();
|
||||
while (WaitForSingleObject(ready, 0) == WAIT_OBJECT_0) {
|
||||
bool ended = take_chunk();
|
||||
while (!ended && WaitForSingleObject(ready, 1000) == WAIT_OBJECT_0) {
|
||||
ended = take_chunk();
|
||||
}
|
||||
if (!ended) {
|
||||
break;
|
||||
}
|
||||
scene.execute(std::span(frame).first(complete), 0, false);
|
||||
frame.erase(frame.begin(), frame.begin() + static_cast<std::ptrdiff_t>(complete));
|
||||
complete = frame.size();
|
||||
++skipped;
|
||||
}
|
||||
|
||||
// Follow the client's 3D view's size.
|
||||
const RECT place = placement(clientWindow, channel->view);
|
||||
const int width = place.right - place.left;
|
||||
const int height = place.bottom - place.top;
|
||||
if (std::memcmp(&place, &placed, sizeof(RECT)) != 0 && width > 0 && height > 0) {
|
||||
SetWindowPos(window, nullptr, place.left, place.top, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
swapChain->Resize(static_cast<Uint32>(width), static_cast<Uint32>(height));
|
||||
placed = place;
|
||||
log("drawing {}x{} at {} in the client's 3D view", width, height, place.left);
|
||||
}
|
||||
LARGE_INTEGER start, end;
|
||||
QueryPerformanceCounter(&start);
|
||||
if (scene.execute(std::span(frame).first(complete), place.left, width > 0 && height > 0)) {
|
||||
swapChain->Present(0);
|
||||
QueryPerformanceCounter(&end);
|
||||
drawTime += static_cast<double>(end.QuadPart - start.QuadPart) / 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);
|
||||
drawTime = 0;
|
||||
drawn = 0;
|
||||
skipped = 0;
|
||||
}
|
||||
}
|
||||
frame.erase(frame.begin(), frame.begin() + static_cast<std::ptrdiff_t>(complete));
|
||||
}
|
||||
|
||||
log("exiting");
|
||||
DestroyWindow(window);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,612 @@
|
||||
#include "scene.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "fixed_function_hlsl.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)
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
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:
|
||||
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;
|
||||
}
|
||||
}
|
||||
return drew;
|
||||
}
|
||||
|
||||
void Scene::setProjection(const protocol::SetProjection& projection)
|
||||
{
|
||||
std::memcpy(constants_.projection, projection.m, sizeof(constants_.projection));
|
||||
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;
|
||||
}
|
||||
// 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) {
|
||||
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}) {
|
||||
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);
|
||||
}
|
||||
|
||||
const Scene::Shaders& Scene::shaders(std::uint32_t streams, std::uint32_t textures)
|
||||
{
|
||||
const std::uint32_t key = streams | (textures << 3);
|
||||
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" + fixedFunctionHlsl;
|
||||
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 = "Fixed function (vertex)";
|
||||
info.EntryPoint = "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)
|
||||
{
|
||||
const std::uint32_t blend = state_.blending ? state_.blendMode : 0;
|
||||
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);
|
||||
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);
|
||||
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 = 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.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;
|
||||
elements[count] = LayoutElement{0, count, 3, VT_FLOAT32, False};
|
||||
++count;
|
||||
if ((streams & hasNormals) != 0) {
|
||||
elements[count] = LayoutElement{1, count, 3, VT_FLOAT32, False};
|
||||
++count;
|
||||
}
|
||||
if ((streams & hasTexCoords) != 0) {
|
||||
elements[count] = LayoutElement{2, count, 2, VT_FLOAT32, False};
|
||||
++count;
|
||||
}
|
||||
if ((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
|
||||
@@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
// Carries out the command stream (include/t5de/protocol/commands.hpp) with Diligent, reproducing IMVU's Direct3D 9
|
||||
// look (shaders/fixed_function.hlsl): the client's textures and buffers by id, its state as it sets it, a pipeline
|
||||
// for each combination of states it draws with.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "EngineFactoryD3D11.h"
|
||||
#include "RefCntAutoPtr.hpp"
|
||||
#include "t5de/protocol/commands.hpp"
|
||||
|
||||
namespace t5de {
|
||||
|
||||
class Scene {
|
||||
public:
|
||||
Scene(Diligent::IRenderDevice* device, Diligent::IDeviceContext* context, Diligent::ISwapChain* swapChain);
|
||||
|
||||
// Carries out one frame's commands. offsetX is where the window starts within the client's 3D view; without draw,
|
||||
// only the frame's changes are made (textures, buffers, state). True if the frame was drawn into the window (to
|
||||
// present).
|
||||
bool execute(std::span<const std::byte> commands, int offsetX, bool draw);
|
||||
|
||||
private:
|
||||
struct Texture {
|
||||
Diligent::RefCntAutoPtr<Diligent::ITexture> texture;
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
std::uint32_t flags = 0;
|
||||
};
|
||||
|
||||
struct Buffer {
|
||||
Diligent::RefCntAutoPtr<Diligent::IBuffer> buffer;
|
||||
protocol::BufferKind kind = protocol::BufferKind::Vertex;
|
||||
std::uint32_t elementSize = 0;
|
||||
std::uint32_t count = 0;
|
||||
};
|
||||
|
||||
struct Pipeline {
|
||||
Diligent::RefCntAutoPtr<Diligent::IPipelineState> state;
|
||||
Diligent::RefCntAutoPtr<Diligent::IShaderResourceBinding> bindings;
|
||||
Diligent::IShaderResourceVariable* texture0 = nullptr;
|
||||
Diligent::IShaderResourceVariable* texture1 = nullptr;
|
||||
};
|
||||
|
||||
struct Shaders {
|
||||
Diligent::RefCntAutoPtr<Diligent::IShader> vertex;
|
||||
Diligent::RefCntAutoPtr<Diligent::IShader> pixel;
|
||||
};
|
||||
|
||||
// Mirrors the shader's constant buffer.
|
||||
struct Constants {
|
||||
float world[16];
|
||||
float view[16];
|
||||
float projection[16];
|
||||
float texTransform0[8];
|
||||
float texTransform1[8];
|
||||
float ambient[4];
|
||||
float lightColor[8][4];
|
||||
float lightVector[8][4];
|
||||
float fogColor[4];
|
||||
float fog[4];
|
||||
float flags[4];
|
||||
};
|
||||
|
||||
// What a draw needs from the state, as set by the client.
|
||||
struct State {
|
||||
bool depthTest = true;
|
||||
bool depthWrite = true;
|
||||
bool blending = false;
|
||||
std::uint32_t blendMode = 1;
|
||||
bool culling = true;
|
||||
bool normalInverting = false;
|
||||
std::uint32_t texture0 = 0;
|
||||
std::uint32_t texture1 = 0;
|
||||
};
|
||||
|
||||
void createTexture(const protocol::Command& command);
|
||||
void updateTexture(const protocol::Command& command);
|
||||
void createBuffer(const protocol::Command& command);
|
||||
void updateBuffer(const protocol::Command& command);
|
||||
void setProjection(const protocol::SetProjection& projection);
|
||||
void setState(const protocol::SetState& state);
|
||||
void drawTriangles(const protocol::DrawTriangles& draw);
|
||||
void drawArrays(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);
|
||||
Diligent::ITextureView* textureView(std::uint32_t id);
|
||||
|
||||
Diligent::RefCntAutoPtr<Diligent::IRenderDevice> device_;
|
||||
Diligent::RefCntAutoPtr<Diligent::IDeviceContext> context_;
|
||||
Diligent::RefCntAutoPtr<Diligent::ISwapChain> swapChain_;
|
||||
Diligent::RefCntAutoPtr<Diligent::IBuffer> constantBuffer_;
|
||||
Diligent::RefCntAutoPtr<Diligent::ISampler> samplers_[4]; // by wrap u (1) and wrap v (2)
|
||||
|
||||
std::unordered_map<std::uint32_t, Texture> textures_;
|
||||
std::unordered_map<std::uint32_t, Buffer> buffers_;
|
||||
std::unordered_map<std::uint32_t, Shaders> shaders_;
|
||||
std::unordered_map<std::uint32_t, Pipeline> pipelines_;
|
||||
|
||||
Constants constants_{};
|
||||
State state_;
|
||||
bool onscreen_ = false; // in a frame drawn into the window
|
||||
bool draw_ = true;
|
||||
int offsetX_ = 0;
|
||||
};
|
||||
|
||||
} // namespace t5de
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
// Generated by CMake from renderer/shaders/@T5DE_SHADER_FILE@: the shader source, compiled at run time by Diligent.
|
||||
|
||||
inline constexpr const char* @T5DE_SHADER_NAME@ = R"hlsl(@T5DE_SHADER_SOURCE@)hlsl";
|
||||
@@ -0,0 +1,117 @@
|
||||
// IMVU's Direct3D 9 fixed-function look, as its renderer sets it up (docs/interface.md):
|
||||
// - per-vertex (Gouraud) lighting, with the vertex color as diffuse and ambient material (white without one): the
|
||||
// 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.
|
||||
//
|
||||
// Compiled for each combination of the vertex streams present (HAS_NORMALS, HAS_TEX_COORDS, HAS_COLORS) and the
|
||||
// number of textures (TEXTURES).
|
||||
|
||||
cbuffer Constants
|
||||
{
|
||||
row_major float4x4 g_World;
|
||||
row_major float4x4 g_View;
|
||||
row_major float4x4 g_Projection;
|
||||
float4 g_TexTransform0[2]; // u' = dot((u, v, 1), [0].xyz), v' = dot((u, v, 1), [1].xyz)
|
||||
float4 g_TexTransform1[2];
|
||||
float4 g_Ambient; // rgb
|
||||
float4 g_LightColor[8]; // rgb, a: enabled
|
||||
float4 g_LightVector[8]; // xyz: position, or direction toward the light; w: 1 if directional
|
||||
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)
|
||||
};
|
||||
|
||||
struct VSInput
|
||||
{
|
||||
float3 Position : ATTRIB0;
|
||||
#if HAS_NORMALS
|
||||
float3 Normal : ATTRIB1;
|
||||
#endif
|
||||
#if HAS_TEX_COORDS
|
||||
float2 TexCoord : ATTRIB2;
|
||||
#endif
|
||||
#if HAS_COLORS
|
||||
float4 Color : ATTRIB3; // bytes B, G, R, A
|
||||
#endif
|
||||
};
|
||||
|
||||
struct PSInput
|
||||
{
|
||||
float4 Position : SV_POSITION;
|
||||
float4 Color : COLOR0;
|
||||
float2 TexCoord0 : TEXCOORD0;
|
||||
float2 TexCoord1 : TEXCOORD1;
|
||||
float Depth : TEXCOORD2;
|
||||
};
|
||||
|
||||
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
|
||||
material = input.Color.zyxw;
|
||||
#endif
|
||||
float4 color = material;
|
||||
if (g_Flags.x > 0.5)
|
||||
{
|
||||
float3 normal = float3(0.0, 0.0, 0.0);
|
||||
#if HAS_NORMALS
|
||||
normal = normalize(mul(input.Normal, (float3x3)g_World));
|
||||
#endif
|
||||
float3 lit = g_Ambient.rgb * material.rgb;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
if (g_LightColor[i].a > 0.5)
|
||||
{
|
||||
float3 toLight = g_LightVector[i].w > 0.5 ? g_LightVector[i].xyz : g_LightVector[i].xyz - world.xyz;
|
||||
lit += g_LightColor[i].rgb * material.rgb * max(0.0, dot(normal, normalize(toLight)));
|
||||
}
|
||||
}
|
||||
color = float4(saturate(lit), material.a);
|
||||
}
|
||||
output.Color = color;
|
||||
|
||||
float3 uv = float3(0.0, 0.0, 1.0);
|
||||
#if HAS_TEX_COORDS
|
||||
uv.xy = input.TexCoord;
|
||||
#endif
|
||||
output.TexCoord0 = float2(dot(uv, g_TexTransform0[0].xyz), dot(uv, g_TexTransform0[1].xyz));
|
||||
output.TexCoord1 = float2(dot(uv, g_TexTransform1[0].xyz), dot(uv, g_TexTransform1[1].xyz));
|
||||
}
|
||||
|
||||
#if TEXTURES >= 1
|
||||
Texture2D g_Texture0;
|
||||
SamplerState g_Texture0_sampler;
|
||||
#endif
|
||||
#if TEXTURES >= 2
|
||||
Texture2D g_Texture1;
|
||||
SamplerState g_Texture1_sampler;
|
||||
#endif
|
||||
|
||||
float4 ps_main(in PSInput input) : SV_Target
|
||||
{
|
||||
float4 color = input.Color;
|
||||
#if TEXTURES == 1
|
||||
color *= g_Texture0.Sample(g_Texture0_sampler, input.TexCoord0);
|
||||
#elif TEXTURES == 2
|
||||
float4 first = g_Texture0.Sample(g_Texture0_sampler, input.TexCoord0);
|
||||
float4 second = g_Texture1.Sample(g_Texture1_sampler, input.TexCoord1);
|
||||
color *= lerp(first, second, second.a);
|
||||
#endif
|
||||
if (g_Flags.y > 0.5 && !(color.a * 255.0 > g_Flags.z))
|
||||
{
|
||||
discard;
|
||||
}
|
||||
if (g_Fog.z > 0.5)
|
||||
{
|
||||
float visible = saturate((g_Fog.y - input.Depth) / 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