I'm trying to render a square at specific locations within a window and so far was able to successfully do so using OpenGL but, for some reason, having trouble doing the same with D3D11.
Here's how I do it using OpenGL:
Vertex shader:
static char vs[] = "#version 450 core \n" "layout (location = 0) in vec2 aVertex; \n" "layout (location = 1) in vec2 aTexture; \n" " \n" "uniform mat4 uProjection; \n" "uniform mat4 uModel; \n" "out vec2 vTexture; \n" " \n" "void main() { \n" " gl_Position = uProjection * uModel * vec4(aVertex, 0.0, 1.0); \n" " vTexture = aTexture; \n" "} \n";
Fragment shader:
static char fs[] = "#version 450 core \n" "out vec4 FragColor; \n" " \n" "uniform vec3 uColor; \n" "uniform sampler2D uSampler; \n" "in vec2 vTexture; \n" " \n" "void main() { \n" " FragColor = vec4(uColor, 1.0) * texture(uSampler, vTexture); \n" "} \n";
The vertices and their corresponding texture coordinates:
GLfloat vertices[] = { // Position // Texture 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f };
For projection, I use an orthographic matrix with a function I implemented myself and return the result in column-major order like so:
static mat4 OrthographicProjection(float left, float right, float bottom, float top, float near, float far) { float a = -(right + left) / (right - left); float b = -(top + bottom) / (top - bottom); float c = -(far + near) / (far - near); mat4 result = {{ 2.0f / (right - left), 0.0f, 0.0f, a, 0.0f, 2.0f / (top - bottom), 0.0f, b, 0.0f, 0.0f, -2.0f / (far - near), c, 0.0f, 0.0f, 0.0f, 1.0f }}; return ColumnMajorOrder(result); }
I then call it and set the uProjection
uniform to its value:
mat4 ortho = OrthographicProjection(0.0f, window_width, window_height, 0.0f, -1.0f, 1.0f); GLint location = glGetUniformLocation(shaderProgram, "uProjection"); glUniformMatrix4fv(location, 1, GL_FALSE, ortho.m);
And, finally, to draw the square, I first apply the transformations I want to it using the Transform()
function which sets the uModel
value, and then render it using the RenderSquare()
function which sets the uColor
value and draws the shape:
static void Transform(int32_t x, int32_t y, uint32_t width, uint32_t height, uint32_t angle) { mat4 model = Identity(); model = Translate(model, x, y, 0.0f); model = Translate(model, 0.5f * width, 0.5f * height, 0.0f); model = Rotate(model, angle, 0.0f, 0.0f, 1.0f); model = Translate(model, -0.5f * width, -0.5f * height, 0.0f); model = Scale(model, width, height, 0.0f); GLint program; glGetIntegerv(GL_CURRENT_PROGRAM, &program); GLint location = glGetUniformLocation(program, "uModel"); glUniformMatrix4fv(location, 1, GL_FALSE, model.m); } static void RenderSquare(int32_t x, int32_t y, uint32_t height, uint32_t width, uint32_t angle, uint32_t color) { Transform(x, y, width, height, angle); float r = ((color >> 16) & 255) / 255.0f; float g = ((color >> 8) & 255) / 255.0f; float b = ((color >> 0) & 255) / 255.0f; GLint program; glGetIntegerv(GL_CURRENT_PROGRAM, &program); GLint location = glGetUniformLocation(program, "uColor"); glUniform3f(location, r, g, b); uint8_t image[4] = {255, 255, 255, 255}; LoadTexture("uSampler", image, 1, 1); glDrawArrays(GL_TRIANGLES, 0, 6); }
Here's how I do it using D3D11:
HLSL shader:
static char hlsl[] = "cbuffer ProjectionBuffer : register(b0) { \n" " float4x4 uProjection; \n" "} \n" " \n" "cbuffer ModelBuffer : register(b1) { \n" " float4x4 uModel; \n" "} \n" " \n" "struct VS_INPUT { \n" " float2 position : POSITION; \n" " float2 texcoord : TEXCOORD; \n" "}; \n" " \n" "struct PS_INPUT { \n" " float4 position : SV_POSITION; \n" " float2 texcoord : TEXCOORD; \n" "}; \n" " \n" "PS_INPUT vs(VS_INPUT input) { \n" " PS_INPUT output; \n" " \n" " float4 pos = float4(input.position, 0.0f, 1.0f); \n" " output.position = mul(uProjection, mul(uModel, pos)); \n" " output.texcoord = input.texcoord; \n" " \n" " return output; \n" "} \n" " \n" "Texture2D tex : register(t0); \n" "SamplerState samplerState : register(s0); \n" " \n" "cbuffer ColorBuffer : register(b2) { \n" " float3 uColor; \n" "} \n" " \n" "float4 ps(PS_INPUT input) : SV_TARGET { \n" " return float4(uColor, 1.0f) * tex.Sample(samplerState, input.texcoord); \n" "} \n";
The same vertices and their corresponding texture coordinates:
float vertices[] = { // Position // Texture 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f };
The same orthographic matrix used for projection which I set the uProjection
buffer value's to:
mat4 ortho = OrthographicProjection(0.0f, window_width, window_height, 0.0f, -1.0f, 1.0f); D3D11_BUFFER_DESC desc = {}; desc.ByteWidth = sizeof(ortho); desc.Usage = D3D11_USAGE_DYNAMIC; desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; ID3D11Device_CreateBuffer(render_device, &desc, 0, &projection_buffer); D3D11_MAPPED_SUBRESOURCE mapped; ID3D11DeviceContext_Map(render_context, (ID3D11Resource *) projection_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped); memcpy(mapped.pData, ortho.m, sizeof(ortho)); ID3D11DeviceContext_Unmap(render_context, (ID3D11Resource *) projection_buffer, 0); ID3D11DeviceContext_VSSetConstantBuffers(render_context, 0, 1, &projection_buffer);
And, finally, to draw the square, I do the exact same thing which is to call the Transform()
function to set the uModel
buffer value to all the applied transformations and then render it using the RenderSquare()
function which sets the uColor
buffer's value and draws the shape:
static void Transform(int32_t x, int32_t y, uint32_t width, uint32_t height, uint32_t angle) { mat4 model = Identity(); model = Translate(model, x, y, 0.0f); model = Translate(model, 0.5f * width, 0.5f * height, 0.0f); model = Rotate(model, angle, 0.0f, 0.0f, 1.0f); model = Translate(model, -0.5f * width, -0.5f * height, 0.0f); model = Scale(model, width, height, 0.0f); D3D11_BUFFER_DESC desc = {}; desc.ByteWidth = sizeof(model); desc.Usage = D3D11_USAGE_DYNAMIC; desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; ID3D11Buffer *buffer; ID3D11Device_CreateBuffer(render_device, &desc, 0, &buffer); D3D11_MAPPED_SUBRESOURCE mapped; ID3D11DeviceContext_Map(render_context, (ID3D11Resource *) buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped); memcpy(mapped.pData, model.m, sizeof(model)); ID3D11DeviceContext_Unmap(render_context, (ID3D11Resource *) buffer, 0); ID3D11DeviceContext_VSSetConstantBuffers(render_context, 1, 1, &buffer); } static void RenderSquare(int32_t x, int32_t y, uint32_t height, uint32_t width, uint32_t angle, uint32_t color) { Transform(x, y, width, height, angle); float rgba[] = { ((color >> 16) & 255) / 255.0f, ((color >> 8) & 255) / 255.0f, ((color >> 0) & 255) / 255.0f, 1.0f }; D3D11_BUFFER_DESC desc = {}; desc.ByteWidth = sizeof(rgba); desc.Usage = D3D11_USAGE_DYNAMIC; desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; ID3D11Buffer *buffer; ID3D11Device_CreateBuffer(render_device, &desc, 0, &buffer); D3D11_MAPPED_SUBRESOURCE mapped; ID3D11DeviceContext_Map(render_context, (ID3D11Resource *) buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped); memcpy(mapped.pData, rgba, sizeof(rgba)); ID3D11DeviceContext_Unmap(render_context, (ID3D11Resource *) buffer, 0); ID3D11DeviceContext_PSSetConstantBuffers(render_context, 2, 1, &buffer); uint8_t image[4] = {255, 255, 255, 255}; LoadTexture(image, 1, 1); ID3D11DeviceContext_Draw(render_context, 6, 0); }
I do want to point out that if I just set the output.position
in the shader like so:
output.position = float4(input.position, 0.0f, 1.0f);
The square is rendered where it supposed to be, I just can't apply any transformations to it since I don't use any matrices...
This leads me to believe that there's some subtle difference between the coordinate systems of these two APIs but I can't seem to find the difference :(
What does "The square is rendered where it supposed to be" means? If it is rendered where it supposed to be then the transformation is applied or not?
And what does "since I don't use any matrices" means? Because you use two float4x4 matrices in hlsl shader.
I'm confused about what exactly is not working for you. Can you share RenderDoc capture and explain what exactly shows up incorrectly in output?
I'm not sure the orthographic matrix is correct.
Emphasis on "not sure".
DirectX uses a NDC space with the depth axis (z) between 0 (near plane) and 1 (far plane), not -1 to 1. So instead of -2/(far-near)
you want -1/(far-near)
(if using left handed it would be 1/(far-near)
). But I don't think it will fix your issue as your z coordinates are always 0.
Another thing that I'm not sure (and will not fix you issue) is if you need those computations in a
, b
and c
. I believe that those computation are to "move" the viewport without moving the "scene". If you always use (0, 0) for left and bottom it won't matter. But if you use left and bottom with other values, you'll move a "window" around the scene, which might be what you want, but the other option is to move the viewport around while keeping the same view of the scene (it's probably not clear what I'm talking about). To do that you'd just pass -1 in both a
and b
, and c
would be -near/(far-near)
. I prefer to use width and height to make it clear that I'm not "moving the viewport" around.
If you could share the whole code (the smallest thing that reproduce the issue and is easy to compile) it would help finding what's going on.
If you want exact same behavior for GL and D3D for handling NDC range then there is glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE)
call you can do.
Yes, I know. But joystick is using the same matrix for both OpenGL and DirectX, and they are trying to make it work on DirectX. I don't think there is a way to configure clip space in directx ? I was just pointing a difference between the two.
Certainly!
Let me clarify what I meant.
When I said "The square is rendered where it supposed to be" and "since I don't use any matrices" I meant that if I assign the output.position
variable to just be the position of the vertices that were given as an input to the shader instead of doing a matrix multiplication and assign its result to the variable like so:
output.position = float4(input.position, 0.0f, 1.0f); // mul(uProjection, mul(uModel, pos));
I get the following result:
And that's to be expected because assigning the vertices as is means that I'll be working in local space with local coordinates which makes the
Transform()
and RenderSquare()
functions useless so if I want to move the square or change its size, I'd have to change its vertices and re-configure its buffers each frame which is cumbersome and costs quite some processing power.
What I want to achieve is what I'd already achieved with OpenGL but using D3D11. I want to be able to call my RenderSquare()
function like so:
RenderSquare(100, 100, 200, 200, 0, 0xff0000);
And get the following result:
To achieve this result, I have to incorporate matrices in my shader code and transform the coordinates from local to clip space where I can specify a range of coordinates in each dimension (In this case I want them to be ranging from 0 to the width/height of the window which is what I specified in my projection matrix) and then I'll be able to freely control my square within the window as seen in the aforementioned result.
However, with D3D11 I had no such luck and I get an empty window when trying to apply the exact same transformations I applied using OpenGL. Even RenderDoc confirmed that I indeed use identical model and projection matrices in both APIs and yet, for some reason, D3D11 refuses to cooperate...
Why are these two APIs behave differently when applying transformations despite my very best attempt at making sure everything is identical, what am I missing here?
You should be able to debug hlsl shader in D3D using Renderdoc to determine what it calculates wrongly. Step line by line and see what it evaluates: https://renderdoc.org/docs/how/how_debug_shader.html
I don't know for sure, but I suspect you just have your order of matrix multiplication wrong - output.position = mul(mul(pos, uModel), uProjection);
Btw you should not be wasting two matrix multiplies like this in vertex shader. Send premultiplied ModelProjection matrix, so you can do just one mul(pos, uModelProjection)
multiply to get output position.
After some debugging, I discovered that even the output vertices are identical:
OpenGL:
VTX IDX gl_Position.x gl_Position.y gl_Position.z gl_Position.w vTexture.x vTexture.y 0 0 -0.85955 0.16667 0 1 0 1 1 1 -0.57865 0.72222 0 1 1 0 2 2 -0.85955 0.72222 0 1 0 0 3 3 -0.85955 0.16667 0 1 0 1 4 4 -0.57865 0.16667 0 1 1 1 5 5 -0.57865 0.72222 0 1 1 0
D3D11:
VTX IDX SV_POSITION.x SV_POSITION.y SV_POSITION.z SV_POSITION.w TEXCOORD.x TEXCOORD.y 0 0 -0.85955 0.16667 0 1 0 1 1 1 -0.57865 0.72222 0 1 1 0 2 2 -0.85955 0.72222 0 1 0 0 3 3 -0.85955 0.16667 0 1 0 1 4 4 -0.57865 0.16667 0 1 1 1 5 5 -0.57865 0.72222 0 1 1 0
And yet, D3D11 presents me with a blank scene... This makes zero sense!
I tried changing the order of multiplication, but that produced vertices with incorrect w component:
VTX IDX SV_POSITION.x SV_POSITION.y SV_POSITION.z SV_POSITION.w TEXCOORD.x TEXCOORD.y 0 0 0 -0.55556 0 301 0 1 1 1 0.2809 0 0 -99 1 0 2 2 0 0 0 1 0 0 3 3 0 -0.55556 0 301 0 1 4 4 0.2809 -0.55556 0 201 1 1 5 5 0.2809 0 0 -99 1 0
Needless to say, the scene remained blank :(
By the way, does the premultiplied ModelProjection matrix tip apply to OpenGL as well? Because I use two matrix multiplies there too
Yes, there's no need to waste extra multiplications in opengl shader too. Do it just once per draw call on CPU, instead of repeating same thing many times (once per vertex).
As for missing rectangle - check the other state of pipeline.
Is your back face culling enabled or front face direction set to opposite value?
Or maybe you have enabled depth testing but have wrong comparison function, or depth buffer is cleared to bad value?
THAT WAS IT
I was using back face culling... I was so fixated on the vertices and the math that I completely forgot to check the rest of the pipeline. Thanks a ton man!
Just a quick question - when closing the window, I'm getting a bunch of warnings:
D3D11 WARNING: Live Object at 0x0000000000479FF0, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN] D3D11 WARNING: Live Object at 0x00000000004B7F70, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN] D3D11 WARNING: Live Object at 0x000000000048EE30, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN] D3D11 WARNING: Live Object at 0x00000000004B6760, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN] D3D11 WARNING: Live Object at 0x00000000004B6950, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
I understand that these warnings indicate live D3D11 objects that haven't been properly released at shutdown or device destruction. However, the messages only show memory addresses and lack context about what the objects actually are, which makes it hard to track down the issue. Is there a way to assign or retrieve debug names for these objects so that the output includes more descriptive information to help identify the source of the leak?
To see more details you need to report live objects before exiting process. To do that, you call ReportLiveDeviceObjects method: https://learn.microsoft.com/en-us/windows/win32/api/d3d11sdklayers/nf-d3d11sdklayers-id3d11debug-reportlivedeviceobjects
And to set names for d3d objects you use SetPrivateData method: https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11devicechild-setprivatedata See the example in Remarks section.