Under normal circumstances, creating a shared handle in DX using CreateSharedHandle, then using glImportMemoryWin32HandleEXT in OpenGL should allow sharing. I tried rendering a triangle in DX and displaying it in OpenGL. When I checked the DX output using RenderDoc, everything seemed fine, but in OpenGL, only the clearColor is displayed, and the triangle is missing. Although I suspect it might be a synchronization issue, I've already set a fence in DX. What is the correct process for sharing textures?
This is the DX output result I found in RenderDoc: enter image description here
However, the texture obtained by OpenGL looks like this: enter image description here
#include <wrl.h>
#include <windows.h>
#include "d3dx12.h"
#include <d3dcompiler.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <vector>
#define _DEBUG
//#define _DXRENDERDOC
#ifdef _DXRENDERDOC
#include "renderdoc_app.h"
#endif
using Microsoft::WRL::ComPtr;
#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "d3dcompiler.lib")
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glew32.lib")
ComPtr<ID3D12Device> d3d12Device;
ComPtr<ID3D12CommandQueue> commandQueue;
ComPtr<ID3D12CommandAllocator> commandAllocator;
ComPtr<ID3D12GraphicsCommandList> commandList;
ComPtr<ID3D12Fence> fence;
UINT64 fenceValue = 0;
ComPtr<ID3D12RootSignature> rootSignature;
ComPtr<ID3D12PipelineState> pipelineState;
ComPtr<ID3D12Resource> vertexBuffer;
D3D12_VERTEX_BUFFER_VIEW vertexBufferView;
HANDLE sharedHandle;
ComPtr<ID3D12Resource> sharedTexture;
GLuint glTexture;
GLuint glMemoryObject;
GLFWwindow* window;
GLuint quadVAO, quadVBO;
GLuint shaderProgram;
struct Vertex {
float position[3];
float color[3];
};
const char* vertexShaderSrc = R"(
struct VSInput {
float3 position : POSITION;
float3 color : COLOR;
};
struct PSInput {
float4 position : SV_POSITION;
float3 color : COLOR;
};
PSInput main(VSInput input) {
PSInput output;
output.position = float4(input.position, 1.0);
output.color = input.color;
return output;
}
)";
const char* pixelShaderSrc = R"(
struct PSInput {
float4 position : SV_POSITION;
float3 color : COLOR;
};
float4 main(PSInput input) : SV_TARGET {
return float4(input.color, 1.0);
}
)";
const char* oglVertexShaderSrc = R"(
#version 450 core
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec2 inTexCoord;
out vec2 TexCoord;
void main()
{
TexCoord = inTexCoord;
gl_Position = vec4(inPosition, 0.0, 1.0);
}
)";
const char* oglFragmentShaderSrc = R"(
#version 450 core
in vec2 TexCoord;
out vec4 FragColor;
uniform sampler2D sharedTexture;
void main()
{
FragColor = texture(sharedTexture, TexCoord);
}
)";
void InitD3D12();
void CreateRootSignature();
void CreateShadersAndPSO();
void CreateVertexBuffer();
void CreateSharedTexture();
void RenderTriangleToTexture();
void WaitForPreviousFrame();
void InitOpenGL();
void DisplayTexture();
void APIENTRY OpenGLDebugMessageCallback(GLenum source, GLenum type, GLuint id,
GLenum severity, GLsizei length,
const GLchar* message, const void* userParam)
{
if (severity == GL_DEBUG_SEVERITY_HIGH) {
std::cerr << "OpenGL Debug Message [" << id << "]: " << message << std::endl;
std::cerr << "Severity: High" << std::endl;
std::cerr << std::endl;
}
else if (severity == GL_DEBUG_SEVERITY_MEDIUM) {
std::cerr << "OpenGL Debug Message [" << id << "]: " << message << std::endl;
std::cerr << "Severity: Medium" << std::endl;
std::cerr << std::endl;
}
else if (severity == GL_DEBUG_SEVERITY_LOW) {
std::cerr << "OpenGL Debug Message [" << id << "]: " << message << std::endl;
std::cerr << "Severity: Low" << std::endl;
std::cerr << std::endl;
}
else if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) {
std::cerr << "OpenGL Debug Message [" << id << "]: " << message << std::endl;
std::cerr << "Severity: Notification" << std::endl;
std::cerr << std::endl;
}
}
void InitOpenGLDebug() {
int flags;
glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) {
std::cout << "Debug context created!" << std::endl;
}
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); // Ensures that errors are reported synchronously
glDebugMessageCallback(OpenGLDebugMessageCallback, nullptr);
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
}
int main() {
InitD3D12();
CreateRootSignature();
CreateShadersAndPSO();
CreateVertexBuffer();
CreateSharedTexture();
#ifdef _DXRENDERDOC
RENDERDOC_API_1_0_0* rdoc_api = nullptr;
HMODULE renderdoc_module = LoadLibraryA("renderdoc.dll");
if (renderdoc_module) {
pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)GetProcAddress(renderdoc_module, "RENDERDOC_GetAPI");
if (RENDERDOC_GetAPI) {
int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_0_0, (void**)&rdoc_api);
if (ret == 1) {
rdoc_api->StartFrameCapture(nullptr, nullptr);
RenderTriangleToTexture();
rdoc_api->EndFrameCapture(nullptr, nullptr);
}
}
else {
std::cerr << "Failed to get RenderDoc API function address." << std::endl;
}
}
else {
std::cerr << "Failed to load renderdoc.dll." << std::endl;
}
#else
RenderTriangleToTexture();
#endif
InitOpenGL();
while (!glfwWindowShouldClose(window)) {
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
DisplayTexture();
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}
void InitD3D12() {
#if defined(_DEBUG)
{
ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)))) {
debugController->EnableDebugLayer();
}
}
#endif
HRESULT hr = D3D12CreateDevice(
nullptr,
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&d3d12Device)
);
if (FAILED(hr)) {
std::cerr << "Failed to create D3D12 device." << std::endl;
exit(EXIT_FAILURE);
}
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
d3d12Device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&commandQueue));
d3d12Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&commandAllocator));
d3d12Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator.Get(), nullptr, IID_PPV_ARGS(&commandList));
d3d12Device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence));
}
void CreateRootSignature() {
D3D12_ROOT_SIGNATURE_DESC rootSignatureDesc = {};
rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
ComPtr<ID3DBlob> signature;
ComPtr<ID3DBlob> error;
HRESULT hr = D3D12SerializeRootSignature(
&rootSignatureDesc,
D3D_ROOT_SIGNATURE_VERSION_1,
&signature,
&error
);
if (FAILED(hr)) {
std::cerr << "Failed to serialize root signature." << std::endl;
exit(EXIT_FAILURE);
}
hr = d3d12Device->CreateRootSignature(
0,
signature->GetBufferPointer(),
signature->GetBufferSize(),
IID_PPV_ARGS(&rootSignature)
);
if (FAILED(hr)) {
std::cerr << "Failed to create root signature." << std::endl;
exit(EXIT_FAILURE);
}
}
void CreateShadersAndPSO() {
ComPtr<ID3DBlob> vertexShader;
ComPtr<ID3DBlob> pixelShader;
ComPtr<ID3DBlob> error;
HRESULT hr = D3DCompile(
vertexShaderSrc, strlen(vertexShaderSrc),
nullptr, nullptr, nullptr,
"main", "vs_5_0",
0, 0,
&vertexShader, &error
);
if (FAILED(hr)) {
std::cerr << "Failed to compile vertex shader." << std::endl;
if (error) {
std::cerr << (char*)error->GetBufferPointer() << std::endl;
}
exit(EXIT_FAILURE);
}
hr = D3DCompile(
pixelShaderSrc, strlen(pixelShaderSrc),
nullptr, nullptr, nullptr,
"main", "ps_5_0",
0, 0,
&pixelShader, &error
);
if (FAILED(hr)) {
std::cerr << "Failed to compile pixel shader." << std::endl;
if (error) {
std::cerr << (char*)error->GetBufferPointer() << std::endl;
}
exit(EXIT_FAILURE);
}
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, offsetof(Vertex, position), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, offsetof(Vertex, color), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
};
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) };
psoDesc.pRootSignature = rootSignature.Get();
psoDesc.VS = { reinterpret_cast<BYTE*>(vertexShader->GetBufferPointer()), vertexShader->GetBufferSize() };
psoDesc.PS = { reinterpret_cast<BYTE*>(pixelShader->GetBufferPointer()), pixelShader->GetBufferSize() };
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState.DepthEnable = FALSE;
psoDesc.DepthStencilState.StencilEnable = FALSE;
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.SampleDesc.Count = 1;
hr = d3d12Device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pipelineState));
if (FAILED(hr)) {
std::cerr << "Failed to create pipeline state." << std::endl;
exit(EXIT_FAILURE);
}
}
void CreateVertexBuffer() {
Vertex triangleVertices[] = {
{ { 0.0f, 0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f } },
{ { 0.5f, -0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f } },
{ { -0.5f, -0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f } },
};
const UINT vertexBufferSize = sizeof(triangleVertices);
CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_UPLOAD);
CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize);
HRESULT hr = d3d12Device->CreateCommittedResource(
&heapProps,
D3D12_HEAP_FLAG_NONE,
&bufferDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&vertexBuffer)
);
if (FAILED(hr)) {
std::cerr << "Failed to create vertex buffer." << std::endl;
exit(EXIT_FAILURE);
}
UINT8* vertexDataBegin;
CD3DX12_RANGE readRange(0, 0);
vertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&vertexDataBegin));
memcpy(vertexDataBegin, triangleVertices, sizeof(triangleVertices));
vertexBuffer->Unmap(0, nullptr);
vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress();
vertexBufferView.StrideInBytes = sizeof(Vertex);
vertexBufferView.SizeInBytes = vertexBufferSize;
}
void CreateSharedTexture() {
D3D12_RESOURCE_DESC textureDesc = {};
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
textureDesc.Width = 512;
textureDesc.Height = 512;
textureDesc.DepthOrArraySize = 1;
textureDesc.MipLevels = 1;
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
textureDesc.SampleDesc.Count = 1;
textureDesc.SampleDesc.Quality = 0;
textureDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
textureDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
D3D12_HEAP_PROPERTIES heapProps = {};
heapProps.Type = D3D12_HEAP_TYPE_DEFAULT;
D3D12_CLEAR_VALUE clearValue = {};
clearValue.Format = textureDesc.Format;
clearValue.Color[0] = 0.0f;
clearValue.Color[1] = 0.0f;
clearValue.Color[2] = 0.0f;
clearValue.Color[3] = 1.0f;
HRESULT hr = d3d12Device->CreateCommittedResource(
&heapProps,
D3D12_HEAP_FLAG_SHARED,
&textureDesc,
D3D12_RESOURCE_STATE_COMMON,
&clearValue,
IID_PPV_ARGS(&sharedTexture)
);
if (FAILED(hr)) {
std::cerr << "Failed to create shared texture." << std::endl;
exit(EXIT_FAILURE);
}
hr = d3d12Device->CreateSharedHandle(
sharedTexture.Get(),
nullptr,
GENERIC_ALL,
nullptr,
&sharedHandle
);
if (FAILED(hr)) {
std::cerr << "Failed to create shared handle." << std::endl;
exit(EXIT_FAILURE);
}
}
void RenderTriangleToTexture() {
WaitForPreviousFrame();
commandAllocator->Reset();
commandList->Reset(commandAllocator.Get(), pipelineState.Get());
commandList->SetGraphicsRootSignature(rootSignature.Get());
D3D12_VIEWPORT viewport;
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = 512;
viewport.Height = 512;
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 1.0f;
commandList->RSSetViewports(1, &viewport);
D3D12_RECT scissorRect = { 0, 0, 512, 512 };
commandList->RSSetScissorRects(1, &scissorRect);
D3D12_RESOURCE_BARRIER ctBarrier = CD3DX12_RESOURCE_BARRIER::Transition(
sharedTexture.Get(),
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_RENDER_TARGET);
commandList->ResourceBarrier(1, &ctBarrier);
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
rtvHeapDesc.NumDescriptors = 1;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
ComPtr<ID3D12DescriptorHeap> rtvHeap;
d3d12Device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&rtvHeap));
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = rtvHeap->GetCPUDescriptorHandleForHeapStart();
d3d12Device->CreateRenderTargetView(sharedTexture.Get(), nullptr, rtvHandle);
commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
const float clearColor[] = { 1.0f, 0.2f, 0.4f, 1.0f };
commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
commandList->IASetVertexBuffers(0, 1, &vertexBufferView);
commandList->DrawInstanced(3, 1, 0, 0);
D3D12_RESOURCE_BARRIER tcBarrier = CD3DX12_RESOURCE_BARRIER::Transition(
sharedTexture.Get(),
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_COMMON);
commandList->ResourceBarrier(1, &tcBarrier);
commandList->Close();
ID3D12CommandList* commandLists[] = { commandList.Get() };
commandQueue->ExecuteCommandLists(_countof(commandLists), commandLists);
WaitForPreviousFrame();
}
void WaitForPreviousFrame() {
fenceValue++;
commandQueue->Signal(fence.Get(), fenceValue);
if (fence->GetCompletedValue() < fenceValue) {
HANDLE eventHandle = CreateEventEx(nullptr, nullptr, false, EVENT_ALL_ACCESS);
fence->SetEventOnCompletion(fenceValue, eventHandle);
WaitForSingleObject(eventHandle, INFINITE);
CloseHandle(eventHandle);
}
}
void InitOpenGL() {
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW." << std::endl;
exit(EXIT_FAILURE);
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
window = glfwCreateWindow(512, 512, "OpenGL Window", nullptr, nullptr);
if (!window) {
std::cerr << "Failed to create GLFW window." << std::endl;
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwMakeContextCurrent(window);
glewExperimental = GL_TRUE;
GLenum glewInitResult = glewInit();
if (GLEW_OK != glewInitResult) {
std::cerr << "Failed to initialize GLEW: "
<< glewGetErrorString(glewInitResult) << std::endl;
exit(EXIT_FAILURE);
}
InitOpenGLDebug();
if (!GLEW_EXT_memory_object || !GLEW_EXT_memory_object_win32) {
std::cerr << "Required OpenGL extensions are not supported." << std::endl;
exit(EXIT_FAILURE);
}
glCreateMemoryObjectsEXT(1, &glMemoryObject);
D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout;
UINT64 totalSize;
D3D12_RESOURCE_DESC resourceDisc = sharedTexture->GetDesc();
d3d12Device->GetCopyableFootprints(&resourceDisc, 0, 1, 0, &layout, nullptr, nullptr, &totalSize);
// If the memory size isn't `width*height*4` due to DX alignment, setting this doesn't help, which is quite frustrating.
// UINT rowPitch = layout.Footprint.RowPitch;
// glPixelStorei(GL_UNPACK_ROW_LENGTH, rowPitch / 4);
if (!GLEW_EXT_memory_object || !GLEW_EXT_memory_object_win32) {
std::cerr << "Required OpenGL extensions are not supported." << std::endl;
exit(EXIT_FAILURE);
}
glImportMemoryWin32HandleEXT(glMemoryObject, totalSize, GL_HANDLE_TYPE_D3D12_RESOURCE_EXT, sharedHandle);
glGenTextures(1, &glTexture);
glBindTexture(GL_TEXTURE_2D, glTexture);
glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, 512, 512, glMemoryObject, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &oglVertexShaderSrc, nullptr);
glCompileShader(vertexShader);
GLint success;
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success) {
char infoLog[512];
glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog);
std::cerr << "Failed to compile OpenGL vertex shader:\n" << infoLog << std::endl;
exit(EXIT_FAILURE);
}
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &oglFragmentShaderSrc, nullptr);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success) {
char infoLog[512];
glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog);
std::cerr << "Failed to compile OpenGL fragment shader:\n" << infoLog << std::endl;
exit(EXIT_FAILURE);
}
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
char infoLog[512];
glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
std::cerr << "Failed to link OpenGL shader program:\n" << infoLog << std::endl;
exit(EXIT_FAILURE);
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
float quadVertices[] = {
// Positions // Texture Coords
-1.0f, 1.0f, 0.0f, 0.0f, // Top-left
-1.0f, -1.0f, 0.0f, 1.0f, // Bottom-left
1.0f, -1.0f, 1.0f, 1.0f, // Bottom-right
-1.0f, 1.0f, 0.0f, 0.0f, // Top-left
1.0f, -1.0f, 1.0f, 1.0f, // Bottom-right
1.0f, 1.0f, 1.0f, 0.0f // Top-right
};
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
glEnableVertexAttribArray(1);
glBindVertexArray(0);
glUseProgram(shaderProgram);
glUniform1i(glGetUniformLocation(shaderProgram, "sharedTexture"), 0);
}
void DisplayTexture() {
glUseProgram(shaderProgram);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, glTexture);
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
}