Продолжаю велосипедить. У меня система рендера (расположенная в библиотеке render.lib) имеет одну четкую задачу - вывод данных на экран... да, да, у всех так, да только не так:)
Я разбил систему рендера на 5 подсистем: device, resource, pipeline, frame, object. Точнее сейчас частично готовы только первые три, две остальные пока только в теории.
Device
Получилась самой простой. В данной системе пока что только один класс RenderDevice, но он является самым основным. Именно с ним работает движок. данный класс делает 4 задачи:
- инициализация DX и других систем рендера
- очищение памяти при выходе
- связывание других систем вместе (чтобы они могли взаимодействовать)
- обработка некоторых событий от окна (изменение окна к примеру). Точнее не так. все эти события уже обработаны в Window, здесь же вызываются методы по мере надобности:
if (m_wnd->IsResize())
m_render->Resize();
То есть данный класс даже рисовать не умеет:) (нет методов Draw, BeginFrame и прочее)
class RenderDevice
{
public:
bool CreateDevice(const DescRender &desc);
void Close();
void Resize();
RenderResource* GetRenderRes() {return renderer_res;}
RenderPipeline* GetRenderPipeline() {return render_pipeline;}
private:
void m_setsize();
bool m_dxgi();
bool m_initDX();
DescRender m_desc;
uint m_width;
uint m_height;
ID3D11Device *device;
ID3D11DeviceContext *devicecontext;
IDXGISwapChain *swapChain;
RenderResource *renderer_res;
RenderPipeline *render_pipeline;
};Здесь и далее весь код - сокращенный псевдокод (то есть убираются некоторые моменты)
Resource
Данная подсистема может ввести вас в заблуждение... она не имеет ничего общего с загрузкой и хранением файлов. Это полностью концепция Direct3D, то есть данные ресурсы, это куски памяти уже подготовленные для передачи на видеокарту (то есть кто-то из уже загрузил в память). Сейчас у меня ресурсы разделены на 4 группы - buffer (индексный, вершинный или константный), shader, state(blend, depth, vertexinput, rasterizer, sampler) и texture. Скорее всего будет добавлена еще одна группа - view (viewport, render target, depth stencil buffer)
Все эти ресурсы управляются и хранятся в классе RenderResource. Данный класс позволяет добавлять новые ресурсы возвращая их идентификатор. Но вообще возможно данный класс будет перенесен в ResourceManager
class RenderResource
{
public:
TextureID AddRenderTarget();
TextureID AddRenderDepth();
SamplerStateID AddSamplerState();
BlendStateID AddBlendState();
DepthStateID AddDepthState();
RasterizerStateID AddRasterizerState();
VertexFormatID AddVertexFormat();
VertexBufferID AddVertexBuffer();
IndexBufferID AddIndexBuffer();
TextureID AddTexture();
ShaderID AddShader();
private:
ID3D11Device *device;
ID3D11DeviceContext *devicecontext;
Array <Texture> textures;
Array <Shader> shaders;
Array <InputLayout> vertexFormats;
Array <VertexBuffer> vertexBuffers;
Array <IndexBuffer> indexBuffers;
Array <SamplerState> samplerStates;
Array <BlendState> blendStates;
Array <DepthState> depthStates;
Array <RasterizerState> rasterizerStates;
};Pipeline
Данную систему я делал с упором на конвеер DX11. Получилось очень близко. Собственно в данной системе формулируется один DIP. То есть после того как мы добавили ресурсы в RenderResource, здесь мы их прикрепляем к конвееру и затем вызываем команду отрисовки.
class RenderPipeline
{
public:
void ChangeRenderTarget();
void Reset();
void SetTexture();
void SetSamplerState();
void SetShader();
void SetVertexFormat();
void SetVertexBuffer();
void SetIndexBuffer();
void SetBlendState()
void SetDepthState();
void SetRasterizerState();
void SetShaderConstant();
void Apply();
private:
InputAssemblerStage IA;
OutputMergerStage OM;
RasterizerStage RS;
TempShaderStage TSS;
};
C помощью сетеров заполняем конвеер, и затем рисуем (DIP). Данный класс содержит члены стадий (так как с шейдерами у меня произошла архитектурная проблемка, за них пока только одна стадия TempShaderStage, потом для каждого вида также будет своя). Об стадиях говорить особо не буду, покажу только пример одной из них:
class InputAssemblerStage
{
public:
void Reset();
void ApplyState();
void SetVertexFormat();
void SetPrimitiveTopology();
void SetVertexBuffer();
void SetIndexBuffer();
private:
void ChangeVertexFormat();
void ChangePrimitiveTopology();
void ChangeVertexBuffer();
void ChangeVertexBuffer();
void ChangeIndexBuffer();
ID3D11DeviceContext *context;
RenderResource *res;
VertexFormatID currentVertexFormat;
VertexFormatID selectedVertexFormat;
VertexBufferID currentVertexBuffers[MAX_VERTEXSTREAM];
VertexFormatID selectedVertexBuffers[MAX_VERTEXSTREAM];
intptr selectedOffsets[MAX_VERTEXSTREAM];
intptr currentOffsets[MAX_VERTEXSTREAM];
IndexBufferID currentIndexBuffer;
IndexBufferID selectedIndexBuffer;
D3D11_PRIMITIVE_TOPOLOGY currTopology;
D3D11_PRIMITIVE_TOPOLOGY selectTopology;
};
То есть RenderPipeline получив Set() зовет нужную команду из одного из своих членов.
Frame
Данная система пока не реализована и только в планах. Она будет отвечать за формирование одного кадра (напомню, pipeline формирует только один DIP). В системе будет один главный класс RenderFrame управляющий всеми другими классами Frame. Также будут следующие классы
RenderView - этот класс будет отвечать за конечное место вывода кадра (то есть именно этот класс рисует на экран). Будет содержать мировые матрицы и вьюпорт. Также здесь будет и Present().
RenderPass - по умолчанию будет один проход, рисующий сразу в RenderView. Но можно будет добавить несколько проходов рисования.
Кроме того данная система будет также собирать данные в пакеты (batch) и сортировать
Object
В данной системе будут более высокоуровневые классы - к примеру меши и материалы. Пока данная система даже не планировалась, так что сказать больше нечего. Возможно ее вообще не будет (меши будут в системе scene)
Кстати, недавно понял что две вещи по отношению к движку - мифы.
1 миф - современный движок нельзя написать одному. Unreal Engine 4 (до его анонса) писался одним человеком (его создателем... но слух непроверен). Unigine (кстати меня разочаровал, когда я его увидел, раньше я думал что он круче) был начат одним человеком. И всеми забытая (и кстати недавно ожившая) nebula 3 развивалась (и развивается) одним человеком (судя по его блогу)
2 миф - движки без проектов нельзя писать... тут есть в лекциях гаймдева одна лекция где описано два подхода и "нет никакого движка, и пишется проект, а общий код переносится в следующие" только один из них. Кроме того оказывается CryEngine 1 является как раз примером такого вот движка в вакууме (анонс в 2000, выпуск в 2002, первая игра - 2004, ощутите разницу, для тех кто скажет что "ну вот они и делали far cry вместе с движком" скажу что движок был написан для презентация NVIDIA, затем на нем собирались делать две другие игры, и только потом far cry)
Нет я конечно даже близко не считаю что смогу до таких результатов дойти (даже не собираюсь - это огромные трудозатраты которые никак не окупятся), просто мотивацию подымаю:)
Выслушаю любые советы и предложения по этой схеме.
Ссылка | Комментарии [54]