Accesso rapido:  

Forum: VirtualDJ Plugins

Topic: Video Effect Output Texture Example - Page: 1
I noticed the videoeffect sdk example doesn't show how to write out the effect? I.E. I see how to get the texture and then process it with my own code, but how do I write result to the DX11 output for virtual to consume for other effects in the chain?
 

Inviato Sun 24 Dec 23 @ 8:08 pm
Tried writing back to the texture supplied but unsure if that's the right way of doing it.
 

Inviato Wed 27 Dec 23 @ 3:37 am
This does not appear to work, so still looking for an updated example on how to write to the DX11 texture for the output in the FX chain.
 

Inviato Wed 27 Dec 23 @ 5:11 pm
djcelPRO InfinityModeratorMember since 2004
 

Inviato Wed 27 Dec 23 @ 9:39 pm
I was hoping it was simpler :), but thanks, I’ll start down this path.
 

Inviato Wed 27 Dec 23 @ 10:59 pm
Not sure what I am doing wrong but I am still seeing the standard output on the VDJ output. Everything is created properly.


TouchObject<TETexture> test;
result = TEInstanceLinkGetTextureValue(instance, "op/vdjtextureout", TELinkValueCurrent, test.take());

if (result == TEResultSuccess && test != nullptr) {

if (TETextureGetType(test) == TETextureTypeD3DShared && result == TEResultSuccess)
{
TouchObject<TED3D11Texture> texture;
result = TED3D11ContextGetTexture(D3DContext, static_cast<TED3DSharedTexture*>(test.get()), texture.take());
if (result != TEResultSuccess)
{
return S_FALSE;
}

devContext->CopyResource(TED3D11TextureGetTexture(texture), D3DTextureOutput);
}
}

if (result != TEResultSuccess)
{
return S_FALSE;
}
devContext->PSSetShader(D3DPixelShader, nullptr, 0);
devContext->PSSetShaderResources(0, 1, &D3DOutputTextureView);

UINT stride = sizeof(TLVERTEX);

UINT offset = 0;

devContext->IASetVertexBuffers(0, 1, &D3DVertexBuffer, &stride, &offset);
devContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
devContext->Draw(4, 0);
}
 

Inviato Thu 28 Dec 23 @ 6:10 pm
Anyone have any thoughts, haven't gotten any further
 

Inviato Wed 03 Jan 24 @ 3:15 pm
AdionPRO InfinityCTOMember since 2006
Here's some code of how it works in the NDI plugin:

Some definitions, TLVERTEX should match the way your pixel shader works.
#define D3DFVF_TLVERTEX 1

struct D3DXCOLOR
{
public:
D3DXCOLOR() = default;
D3DXCOLOR(FLOAT r, FLOAT g, FLOAT b, FLOAT a)
{
this->r = r;
this->g = g;
this->b = b;
this->a = a;
}

operator FLOAT* ()
{
return &r;
}

FLOAT r, g, b, a;
};

struct TLVERTEX
{
FLOAT x, y, z; // position
D3DXCOLOR colour; // color
FLOAT u, v; // texture coordinate
};


A pretty minimal pixel shader
Texture2D shaderTexture;
SamplerState SampleType;

float4 main(float4 position : SV_Position, float4 color : COLOR, float2 texcoord : TEXCOORD) : SV_TARGET0
{
float4 output;
output = shaderTexture.Sample(SampleType, texcoord);
output = output * color;

return output;
}


If you keep this layout, you also don't need a vertex shader, as the default one that VirtualDJ already set will be fine.

Helper function to create a texture:
	ID3D11Texture2D* createTexture(ID3D11Device *d3dDev, int w, int h, DXGI_FORMAT format, D3D11_SUBRESOURCE_DATA *data, ID3D11ShaderResourceView **view)
{
D3D11_TEXTURE2D_DESC desc{ 0 };
desc.Width = w;
desc.Height = h;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = format ? format : DXGI_FORMAT_B8G8R8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
desc.CPUAccessFlags = 0;
desc.MiscFlags = 0;

ID3D11Texture2D* d3dTexture = nullptr;
HRESULT res = d3dDev->CreateTexture2D(&desc, data, &d3dTexture);

if (d3dTexture)
{
D3D11_SHADER_RESOURCE_VIEW_DESC viewDesc{};
viewDesc.Format = desc.Format;
if (desc.Format==DXGI_FORMAT_NV12)
viewDesc.Format = DXGI_FORMAT_R8_UNORM;
else if (desc.Format==DXGI_FORMAT_P010 || desc.Format==DXGI_FORMAT_P016)
viewDesc.Format = DXGI_FORMAT_R16_UNORM;
else if (desc.Format==DXGI_FORMAT_YUY2) // https://msdn.microsoft.com/en-us/library/bb173059(v=vs.85).aspx
viewDesc.Format = DXGI_FORMAT_R8G8B8A8_UINT;
viewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
viewDesc.Texture2D.MipLevels = -1;
d3dDev->CreateShaderResourceView(d3dTexture, &viewDesc, view);
if (!*view)
{
DebugBreak();
}
}

return d3dTexture;
}


Helper functions to fill the vertices:
	template<class T> void setVertexDst(T* vertices, float dstX, float dstY, float width, float height, DWORD col)
{
if (!vertices)
return;

const D3DXCOLOR color = D3DXCOLOR(GetRValue(col)/255.f, GetGValue(col)/255.f, GetBValue(col)/255.f, 1.f);
vertices[0].colour = color;
vertices[0].x = dstX+width;
vertices[0].y = dstY;
vertices[0].z = 0.0f;

vertices[1].colour = color;
vertices[1].x = dstX + width;
vertices[1].y = dstY + height;
vertices[1].z = 0.0f;

vertices[2].colour = color;
vertices[2].x = dstX;
vertices[2].y = dstY + height;
vertices[2].z = 0.0f;

vertices[3].colour = color;
vertices[3].x = dstX;
vertices[3].y = dstY + height;
vertices[3].z = 0.0f;

vertices[4].colour = color;
vertices[4].x = dstX;
vertices[4].y = dstY;
vertices[4].z = 0.0f;

vertices[5].colour = color;
vertices[5].x = dstX + width;
vertices[5].y = dstY;
vertices[5].z = 0.0f;
}

template<class T> void setVertexSrc(T* vertices, float srcX, float srcY, float srcWidth, float srcHeight, float textureWidth, float textureHeight)
{
if (!vertices)
return;

vertices[0].u = (srcX+srcWidth) / textureWidth;
vertices[0].v = srcY / textureHeight;

vertices[1].u = (srcX+srcWidth) / textureWidth;
vertices[1].v = (srcY+srcHeight) / textureHeight;

vertices[2].u = srcX / textureWidth;
vertices[2].v = (srcY+srcHeight) / textureHeight;

vertices[3].u = srcX / textureWidth;
vertices[3].v = (srcY+srcHeight) / textureHeight;

vertices[4].u = srcX / textureWidth;
vertices[4].v = srcY / textureHeight;

vertices[5].u = (srcX+srcWidth) / textureWidth;
vertices[5].v = srcY / textureHeight;
}


Helper function to draw a texture
	void bitblt(ID3D11Device* d3dDev, ID3D11ShaderResourceView* textureView)
{
int nbVertices = 6;

if (!vertexBuffer)
{
D3D11_BUFFER_DESC bd{ 0 };
bd.Usage = D3D11_USAGE_DYNAMIC; // write access access by CPU and GPU
bd.ByteWidth = sizeof(TLVERTEX) * nbVertices;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; // use as a vertex buffer
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; // allow CPU to write in buffer
HRESULT hr = d3dDev->CreateBuffer(&bd, nullptr, &vertexBuffer.p);
if (!vertexBuffer)
return;
}

D3D11_MAPPED_SUBRESOURCE ms;
HRESULT hr = devContext->Map(vertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &ms); // map the buffer
if (hr!=S_OK)
return;

int srcX=0, srcY=0, srcWidth=curTextureW, srcHeight=curTextureH, dstX=0, dstY=0, dstWidth=width, dstHeight=height;
initImageSize(&srcX, &srcY, &srcWidth, &srcHeight, curTextureAR, 0, width, height, &dstX, &dstY, &dstWidth, &dstHeight);

TLVERTEX *vertices = (TLVERTEX*)ms.pData;
setVertexDst(vertices, (float)dstX, (float)dstY, (float)dstWidth, (float)dstHeight, RGB(255, 255, 255));
setVertexSrc(vertices, (float)srcX, (float)srcY, (float)srcWidth, (float)srcHeight, (float)curTextureW, (float)curTextureH);
devContext->Unmap(vertexBuffer, 0);
UINT stride = sizeof(TLVERTEX);
UINT offset = 0;
devContext->IASetVertexBuffers(0, 1, &vertexBuffer.p, &stride, &offset);

devContext->OMSetBlendState(nullptr, nullptr, 0xffffffff);
if (!pPS_Alpha)
{
std::string_view PS_Alpha = getResource(L"pshader.cso");
hr = d3dDev->CreatePixelShader(PS_Alpha.data(), PS_Alpha.length(), nullptr, &pPS_Alpha);
}
devContext->PSSetShader(pPS_Alpha, nullptr, 0);

devContext->PSSetShaderResources(0, 1, &textureView);

devContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
devContext->Draw(6, 0);
}


OnDraw() to actually draw a texture
	HRESULT VDJ_API OnDraw() override
{
std::lock_guard<std::mutex> ATS(threadSync);

ID3D11Device* d3dDev = nullptr; //GetDevice doesn't AddRef, so doesn't need to be released
if (GetDevice(VdjVideoEngineDirectX11, (void**)&d3dDev)!=S_OK)
return E_FAIL;

if (!devContext)
{
d3dDev->GetImmediateContext(&devContext.p);
if (!devContext)
return E_FAIL;
}

if (curTextureView)
bitblt(d3dDev, curTextureView);

return S_OK;
}


Creating a texture with data in memory:
				CComPtr<ID3D11Device> d3dDev;
{
std::lock_guard<std::mutex> ATS(threadSync);
if (devContext)
devContext->GetDevice(&d3dDev.p);
}

if (d3dDev)
{
D3D11_SUBRESOURCE_DATA frameData{ 0 };
frameData.pSysMem = video_frame.p_data;
frameData.SysMemPitch = video_frame.line_stride_in_bytes;
frameData.SysMemSlicePitch = video_frame.line_stride_in_bytes * video_frame.yres;
CComPtr<ID3D11Texture2D> newTexture;
CComPtr<ID3D11ShaderResourceView> newView;
newTexture.Attach(createTexture(d3dDev.p, video_frame.xres, video_frame.yres, DXGI_FORMAT_B8G8R8A8_UNORM, &frameData, &newView.p));
if (newTexture)
{
std::lock_guard<std::mutex> ATS(threadSync);
curTexture = newTexture;
curTextureView = newView;
}
}


Would indeed be useful to make a clean example from this, but I think this should get you started.
 

Inviato Tue 09 Jan 24 @ 9:30 am
Thanks! This is much more complex than the audio DSP processing haha. i wish there was a way to use a CopyResource function call on a texture instead. but I will get to working on this.
 

Inviato Tue 09 Jan 24 @ 5:23 pm
AdionPRO InfinityCTOMember since 2006
DirectX 11 indeed requires a bit of code to get started, but once you get through the basics it's not too bad
 

Inviato Tue 09 Jan 24 @ 6:23 pm
In this case it appears data is being loaded from CPU. In my case I have access to a DX11 texture source that i need to copy to VDJ. Would this still work?
 

Inviato Wed 10 Jan 24 @ 12:52 am
Also the initImageSize function, do you have a declaraion for what that does.
 

Inviato Wed 10 Jan 24 @ 1:32 am
AdionPRO InfinityCTOMember since 2006
 

Inviato Wed 10 Jan 24 @ 4:49 am
AdionPRO InfinityCTOMember since 2006
initImageSize is just a helper to allow for crop/zoom/stretch to be handled easier
void initImageSize(int *srcX, int *srcY, int *srcWidth, int *srcHeight, float srcAr, int srcOrientation, int width, int height, int* dstX, int* dstY, int* dstWidth, int* dstHeight)
{
int letterBoxing = 0;
*dstWidth = width;
*dstHeight= height;
*dstX = 0;
*dstY = 0;
if (letterBoxing==2) //zoom (stretch)
return;
if (srcWidth==0 || srcHeight==0)
return;

if (srcOrientation>=6&&srcOrientation<=8)
{
*srcWidth = *srcHeight;
*srcHeight = *srcWidth;
}

if (letterBoxing==0) //borders (scale)
{
*dstWidth = (int)(height * *srcWidth*srcAr / *srcHeight);
*dstHeight= height;
if (*dstWidth > width)
{
*dstWidth = width;
*dstHeight= (int)(width * *srcHeight / (*srcWidth*srcAr));
}
*dstX = (width - *dstWidth)/2;
*dstY = (height- *dstHeight)/2;
}
else if (letterBoxing==1) //crop
{
*dstWidth = (int)(height * *srcWidth*srcAr / *srcHeight);
*dstHeight= height;
if (*dstWidth < width)
{
*dstWidth = width;
*dstHeight= (int)(width * *srcHeight / (*srcWidth*srcAr));
}
*dstX = (width - *dstWidth)/2;
*dstY = (height- *dstHeight)/2;
//TODO: Is this correct in combination with rotation?
if (*dstX<0)
{
*srcX = -(*dstX * *srcWidth / *dstWidth);
*srcWidth -= 2 * *srcX;
*dstWidth -= *dstX*2;
*dstX = 0;
}
if (*dstY<0)
{
*srcY = -(*dstY * *srcHeight / *dstHeight);
*srcHeight -= 2 * *srcY;
*dstHeight -= *dstY*2;
*dstY = 0;
}
}
}
 

Inviato Wed 10 Jan 24 @ 4:51 am
 

Inviato Thu 11 Jan 24 @ 2:08 am
Well, I'm closer, getting half of the screen black diagonally.
 

Inviato Thu 11 Jan 24 @ 2:24 am
Any recommendations on debugging what could be preventing the texture from passing through and the diagonal black split on the screen? I'm guessing it has to do with the vertices not being correct, but I've copied bit for bit pretty much what you supplied.
 

Inviato Sun 14 Jan 24 @ 10:16 pm
djcelPRO InfinityModeratorMember since 2004
I think it's indeed vertices too.
Drawing is based on 2 triangles ("D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST") and the index of the vertices is important. (Clockwise or counterclockwise)
Can you post a screenshot so we can see it?
 

Inviato Mon 15 Jan 24 @ 4:54 pm
 

Inviato Mon 15 Jan 24 @ 8:42 pm
djcelPRO InfinityModeratorMember since 2004
Adion's code should be ok but here is another way to write the vertices

struct D3DPOSITION
{
float x;
float y;
float z;
};

struct D3DTEXCOORD
{
float tu;
float tv;
};

struct D3DXCOLOR
{
public:
D3DXCOLOR() = default;
D3DXCOLOR(FLOAT r, FLOAT g, FLOAT b, FLOAT a)
{
this->r = r;
this->g = g;
this->b = b;
this->a = a;
}

operator FLOAT* ()
{
return &r;
}

FLOAT r, g, b, a;
};

struct TLVERTEX
{
D3DPOSITION position;
D3DXCOLOR color;
D3DTEXCOORD textcoord;
};

TLVERTEX pNewVertices[6];


void CMyPlugin::UpdateVertices_D3D11()
{
pos_x = Xo * width; // 0
pos_y = Yo * height; // 0
pos_width =(Xmax - Xo) * width; // width
pos_height = (Ymax - Yo) * height; // height


D3DPOSITION P1 = { pos_x , pos_y , 0.0f }, // Top Left
P2 = { pos_x , pos_height + pos_y * 2 , 0.0f }, // Bottom Left
P3 = { pos_width + pos_x * 2 , pos_y , 0.0f }, // Top Right
P4 = { pos_width + pos_x * 2 , pos_height + pos_y * 2 , 0.0f }; // Bottom Right
D3DXCOLOR color_vertex = D3DXCOLOR(1.0f, 1.0f, 1.0f, alpha);
D3DTEXCOORD T1 = { 0.0f , 0.0f }, T2 = { 0.0f , 1.0f }, T3 = { 1.0f , 0.0f }, T4 = { 1.0f , 1.0f };


// Triangle n°1 (Bottom Right)
pNewVertices[0] = { P3 , color_vertex , T3 };
pNewVertices[1] = { P4 , color_vertex , T4 };
pNewVertices[2] = { P2 , color_vertex , T2 };

// Triangle n°2 (Top Left)
pNewVertices[3] = { P2 , color_vertex , T2 };
pNewVertices[4] = { P1 , color_vertex , T1 };
pNewVertices[5] = { P3 , color_vertex , T3 };
}

 

Inviato Sun 21 Jan 24 @ 8:02 pm
80%