I am building a 3d renderer in C++ using Vulkan. I am using the C++ implementation of Vulkan. I've been trying to implement shadow mapping to my renderer. I've set up a separate render pass and command buffer to handle the shadow mapping and I have another render pass and command buffer to render the scene. I am no longer getting any validation errors when running the renderer. I am now trying to validate that the shadow mapping is working as intended by rendering the outputs to a texture within the scene. The transitions between the depth stencil and the output image are being handled using subpass dependencies in the shadow render pass. While testing the shadow mapping output, the resulting texture is now entirely red. Do you know what might be causing this problem?

Here is some of the code:

Shadow Render Pass:

vk::AttachmentDescription depthAttachment{};
    depthAttachment.format = FindDepthFormat(*GetPhysicalDevice()); // High precision format
    depthAttachment.samples = vk::SampleCountFlagBits::e1;
    depthAttachment.loadOp = vk::AttachmentLoadOp::eClear;
    depthAttachment.storeOp = vk::AttachmentStoreOp::eStore;
    depthAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
    depthAttachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
    depthAttachment.initialLayout = vk::ImageLayout::eUndefined;
    depthAttachment.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal;

    vk::AttachmentReference depthAttachmentRef{};
    depthAttachmentRef.attachment = 0;
    depthAttachmentRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;

    vk::SubpassDescription subpass{};
    subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
    subpass.colorAttachmentCount = 0;
    subpass.pDepthStencilAttachment = &depthAttachmentRef;

    // Use subpass dependencies for layout transitions
    std::array<vk::SubpassDependency, 2> dependencies;

    dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
    dependencies[0].dstSubpass = 0;
    dependencies[0].srcStageMask = vk::PipelineStageFlagBits::eFragmentShader;// VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
    dependencies[0].dstStageMask = vk::PipelineStageFlagBits::eEarlyFragmentTests;// VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
    dependencies[0].srcAccessMask = vk::AccessFlagBits::eShaderRead; //VK_ACCESS_SHADER_READ_BIT;
    dependencies[0].dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite; //VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
    dependencies[0].dependencyFlags = vk::DependencyFlagBits::eByRegion; //VK_DEPENDENCY_BY_REGION_BIT;

    dependencies[1].srcSubpass = 0;
    dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
    dependencies[1].srcStageMask = vk::PipelineStageFlagBits::eLateFragmentTests;// VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
    dependencies[1].dstStageMask = vk::PipelineStageFlagBits::eFragmentShader; //VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
    dependencies[1].srcAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite; //VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
    dependencies[1].dstAccessMask = vk::AccessFlagBits::eShaderRead;// VK_ACCESS_SHADER_READ_BIT;
    dependencies[1].dependencyFlags = vk::DependencyFlagBits::eByRegion;// VK_DEPENDENCY_BY_REGION_BIT;


    vk::RenderPassCreateInfo renderPassInfo{};
    renderPassInfo.attachmentCount = 1;
    renderPassInfo.pAttachments = &depthAttachment;
    renderPassInfo.subpassCount = 1;
    renderPassInfo.pSubpasses = &subpass;
    renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
    renderPassInfo.pDependencies = dependencies.data();

    if (device.createRenderPass(&renderPassInfo, nullptr, &shadowRenderPass) != vk::Result::eSuccess)
    {
        throw std::runtime_error("Failed to create render pass.");
    }


The Begin Shadow Frame:

// Wait for any previous work on this swapchain image to complete
    device.waitForFences(1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
    device.resetFences(1, &inFlightFences[currentFrame]);

    // Acquire an image from the swapchain, then begin recording commands for the current frame.
    vk::Result acquireResult = device.acquireNextImageKHR(swapchain, UINT64_MAX, imageAvailableSemaphores[currentFrame], vk::Fence(), &imageIndex);

    if (acquireResult != vk::Result::eSuccess && acquireResult != vk::Result::eSuboptimalKHR) {
        throw std::runtime_error("Failed to acquire next swapchain image.");
    }

    vk::CommandBufferBeginInfo beginInfo{};
    beginInfo.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;

    // Shadow Pass
    shadowCommandBuffers[0].begin(beginInfo);

    vk::RenderPassBeginInfo shadowPassInfo{};
    shadowPassInfo.renderPass = shadowRenderPass;
    shadowPassInfo.framebuffer = shadowFramebuffers[0];
    shadowPassInfo.renderArea.offset = vk::Offset2D{ 0, 0 };
    shadowPassInfo.renderArea.extent = vk::Extent2D(SHADOW_WIDTH, SHADOW_HEIGHT);

    vk::ClearValue shadowClearValue = { vk::ClearDepthStencilValue(1.0f, 0) };
    shadowPassInfo.clearValueCount = 1;
    shadowPassInfo.pClearValues = &shadowClearValue;

    shadowCommandBuffers[0].beginRenderPass(shadowPassInfo, vk::SubpassContents::eInline);
    // (Render scene from light perspective)

The End Shadow Frame:

    shadowCommandBuffers[0].endRenderPass();

    shadowCommandBuffers[0].end();

    vk::SubmitInfo shadowSubmitInfo{};
    shadowSubmitInfo.commandBufferCount = 1;
    shadowSubmitInfo.pCommandBuffers = &shadowCommandBuffers[0];

    vk::Semaphore waitSemaphores[] = { imageAvailableSemaphores[currentFrame] };
    vk::PipelineStageFlags waitStages[] = { vk::PipelineStageFlagBits::eColorAttachmentOutput };
    shadowSubmitInfo.waitSemaphoreCount = 1;
    shadowSubmitInfo.pWaitSemaphores = waitSemaphores;
    shadowSubmitInfo.pWaitDstStageMask = waitStages;

    vk::Semaphore signalSemaphores[] = { shadowMapCompletedSemaphore };
    shadowSubmitInfo.signalSemaphoreCount = 1;
    shadowSubmitInfo.pSignalSemaphores = signalSemaphores;

    graphicsQueue.submit(1, &shadowSubmitInfo, nullptr);

The Shadow Image creation:

        imageInfo.imageType = vk::ImageType::e2D;
        imageInfo.format = FindDepthFormat(*GetPhysicalDevice();
        imageInfo.extent = vk::Extent3D(SHADOW_WIDTH, SHADOW_HEIGHT, 1);
        imageInfo.mipLevels = 1;
        imageInfo.arrayLayers = 1;
        imageInfo.samples = vk::SampleCountFlagBits::e1;
        imageInfo.tiling = vk::ImageTiling::eOptimal;
        imageInfo.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled;
        imageInfo.sharingMode = vk::SharingMode::eExclusive;

The Shadow Render:

VkDevice device = *renderer.GetDevice();

	//glm::mat4 modelMatrix;
	glm::vec3 position = glm::vec3(0.0f, 0.0f, 10.0f);
	glm::vec3 rotation = glm::vec3(0.0f, 0.0f, 0.0f);
	glm::vec3 scale = glm::vec3(0.2f);

	glm::vec3 position2 = light.position;// glm::vec3(0.0f, 5.0f, 0.0f);//
	glm::vec3 position3 = glm::vec3(0.0f, 0.0f, 0.0f);

	// Bind mesh vertex and index buffers
	VkBuffer vertexBuffers[] = { meshes[0].get()->GetVertexBuffer() };
	VkDeviceSize offsets[] = { 0 };
	vkCmdBindVertexBuffers(renderer.shadowCommandBuffers[0], 0, 1, vertexBuffers, offsets);
	vkCmdBindIndexBuffer(renderer.shadowCommandBuffers[0], meshes[0].get()->GetIndexBuffer(), 0, VK_INDEX_TYPE_UINT32);


	glm::mat4 modelMatrix[] = { glm::mat4(1.0f),glm::mat4(1.0f),glm::mat4(1.0f) };
	modelMatrix[0] = glm::translate(modelMatrix[0], position);
	modelMatrix[1] = glm::translate(modelMatrix[1], position2);
	modelMatrix[2] = glm::translate(modelMatrix[2], position3);

	modelMatrix[1] = glm::scale(modelMatrix[1], scale);

	BufferUtils::UpdateBuffer(device, modelMatrixBufferobj.memory, sizeof(glm::mat4) * modelcount, modelMatrix);

	float near_plane = 1.0f, far_plane = 7.5f;
	glm::mat4 lightProjection = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane);

	glm::mat4 lightView = glm::lookAt(light.position,
		glm::vec3(0.0f, 0.0f, 0.0f),
		glm::vec3(0.0f, 1.0f, 0.0f));

	glm::mat4 lightSpaceMatrix = lightProjection * lightView;

	BufferUtils::UpdateBuffer(device, lightSpaceBufferobj.memory, sizeof(glm::mat4), &lightSpaceMatrix);

	vkDeviceWaitIdle(device);

	// Geometry Pass

	VkDescriptorSet shadowSet = this->shadowdescriptorSet.get();
	vkCmdBindPipeline(renderer.shadowCommandBuffers[0], VK_PIPELINE_BIND_POINT_GRAPHICS, renderer.GetPipeline(3).get()->GetPipeline());
	vkCmdBindDescriptorSets(renderer.shadowCommandBuffers[0], VK_PIPELINE_BIND_POINT_GRAPHICS, shadowpipelineLayout, 0, 1, &shadowSet, 0, nullptr);

	uint32_t numIndices4 = static_cast<uint32_t>(meshes[0].get()->GetIndices().size());
	vkCmdDrawIndexed(renderer.shadowCommandBuffers[0], numIndices4, modelcount, 0, 0, 0);

The Scene Render:

VkDevice device = *renderer.GetDevice();

	//glm::mat4 modelMatrix;
	glm::vec3 position = glm::vec3(0.0f, 0.0f, 10.0f);
	glm::vec3 rotation = glm::vec3(0.0f, 0.0f, 0.0f);
	glm::vec3 scale = glm::vec3(0.2f);

	glm::vec3 position2 = light.position;// glm::vec3(0.0f, 5.0f, 0.0f);//
	glm::vec3 position3 = glm::vec3(0.0f, 0.0f, 0.0f);

	// Bind mesh vertex and index buffers
	VkBuffer vertexBuffers[] = { meshes[0].get()->GetVertexBuffer() };
	VkDeviceSize offsets[] = { 0 };
	vkCmdBindVertexBuffers(*renderer.GetCurrentCommandBuffer(), 0, 1, vertexBuffers, offsets);
	vkCmdBindIndexBuffer(*renderer.GetCurrentCommandBuffer(), meshes[0].get()->GetIndexBuffer(), 0, VK_INDEX_TYPE_UINT32);


	glm::mat4 modelMatrix[] = { glm::mat4(1.0f),glm::mat4(1.0f),glm::mat4(1.0f) };
	modelMatrix[0] = glm::translate(modelMatrix[0], position);
	modelMatrix[1] = glm::translate(modelMatrix[1], position2);
	modelMatrix[2] = glm::translate(modelMatrix[2], position3);

	modelMatrix[1] = glm::scale(modelMatrix[1], scale);

	//modelcount = 2;

	AmbientLight ambientUBO = {};

	ambientUBO.color = { 1.0, 1.0, 1.0 };
	ambientUBO.intensity = 0.05;

	DirectionalLight directionalUBO = {};

	//directionalUBO.position = { 1.0, 1.0, 1.0 };
	directionalUBO.color = { 1.0, 1.0, 1.0 };
	directionalUBO.intensity = 1.0;

	directionalUBO.position = light.position;

	SpecularData specularUBO = {};

	specularUBO.intensity = 0.5;
	specularUBO.shininess = 32;

	CameraData cameraUBO = {};

	cameraUBO.position = camera.GetPosition();

	BufferUtils::UpdateBuffer(device, modelMatrixBufferobj.memory, sizeof(glm::mat4) * modelcount, modelMatrix);
	BufferUtils::UpdateBuffer(device, viewMatrixBufferobj.memory, sizeof(glm::mat4), &camera.GetViewMatrix());
	BufferUtils::UpdateBuffer(device, projMatrixBufferobj.memory, sizeof(glm::mat4), &camera.GetProjectionMatrix());
	BufferUtils::UpdateBuffer(device, modelCountBufferobj.memory, sizeof(int), &modelcount);
	BufferUtils::UpdateBuffer(device, ambientLightBufferobj.memory, sizeof(AmbientLight), &ambientUBO);
	BufferUtils::UpdateBuffer(device, directionalLightBufferobj.memory, sizeof(DirectionalLight), &directionalUBO);
	BufferUtils::UpdateBuffer(device, specularDataBufferobj.memory, sizeof(SpecularData), &specularUBO);
	BufferUtils::UpdateBuffer(device, cameraDataBufferobj.memory, sizeof(SpecularData), &cameraUBO);


	vkDeviceWaitIdle(device);

	// Geometry Pass

	VkDescriptorSet geomSet = this->geomdescriptorSet.get();
	vkCmdBindPipeline(*renderer.GetCurrentCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, renderer.GetPipeline(0).get()->GetPipeline());
	vkCmdBindDescriptorSets(*renderer.GetCurrentCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, geompipelineLayout, 0, 1, &geomSet, 0, nullptr);

	uint32_t numIndices4 = static_cast<uint32_t>(meshes[0].get()->GetIndices().size());
	vkCmdDrawIndexed(*renderer.GetCurrentCommandBuffer(), numIndices4, modelcount, 0, 0, 0);


	VkBuffer vertexBuffers2[] = { meshes[1].get()->GetVertexBuffer() };
	VkDeviceSize offsets2[] = { 0 };
	vkCmdBindVertexBuffers(*renderer.GetCurrentCommandBuffer(), 0, 1, vertexBuffers2, offsets2);
	vkCmdBindIndexBuffer(*renderer.GetCurrentCommandBuffer(), meshes[1].get()->GetIndexBuffer(), 0, VK_INDEX_TYPE_UINT32);
	glm::vec3 position4 = glm::vec3(0.0f, 0.0f, 0.0f);

	glm::vec3 scale2 = glm::vec3(5.0f);
	glm::mat4 modelMatrix2[] = { glm::mat4(1.0f)};
	modelMatrix2[0] = glm::translate(modelMatrix2[0], position4);
	modelMatrix2[0] = glm::scale(modelMatrix2[0], scale2);

	BufferUtils::UpdateBuffer(device, modelMatrix2Bufferobj.memory, sizeof(glm::mat4) * 1, modelMatrix2);

	VkDescriptorSet geomSet2 = this->geomdescriptorSet2.get();

	vkCmdBindPipeline(*renderer.GetCurrentCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, renderer.GetPipeline(2).get()->GetPipeline());
	vkCmdBindDescriptorSets(*renderer.GetCurrentCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, geompipelineLayout, 0, 1, &geomSet2, 0, nullptr);
	vkCmdDrawIndexed(*renderer.GetCurrentCommandBuffer(), numIndices4, 1, 0, 0, 0);

	//vkCmdDrawIndexed(*renderer.GetCurrentCommandBuffer(), numIndices4, 1, 0, 0, 0);

	//// Transition to Lighting Pass
	 
	renderer.GetCurrentCommandBuffer()->nextSubpass(vk::SubpassContents::eInline);

	//// Lighting Pass

	VkDescriptorSet lightSet = this->lightdescriptorSet.get();
	vkCmdBindPipeline(*renderer.GetCurrentCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, renderer.GetPipeline(1).get()->GetPipeline());
	vkCmdBindDescriptorSets(*renderer.GetCurrentCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, lightpipelineLayout, 0, 1, &lightSet, 0, nullptr);

	// Draw fullscreen quad
	renderer.GetCurrentCommandBuffer()->draw(6, 1, 0, 0);

Shadow Vert Shader:

#version 450

layout(location = 0) in vec3 inPosition;

layout(binding = 0) buffer ModelBufferObject {
    mat4 models[];
} modelUBO;

layout(binding = 1) uniform LightSpaceBufferObject {
    mat4 lightSpaceMatrix;
} lsUBO;

void main()
{
    gl_Position = lsUBO.lightSpaceMatrix * modelUBO.models[gl_InstanceIndex] * vec4(inPosition, 1.0);
}  


Geometry Vert Shader:

#version 450

layout(binding = 0) buffer ModelBufferObject {
    mat4 models[];
} modelUBO;

layout(binding = 1) uniform ViewBufferObject {
    mat4 view;
} viewUBO;

layout(binding = 2) uniform ProjBufferObject {
    mat4 proj;
} projUBO;

layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec2 inTexCoord;
layout(location = 3) in vec3 inNormal;

layout(location = 0) out vec3 fragPosition;
layout(location = 1) out vec3 fragNormal;
layout(location = 2) out vec3 fragColor;
layout(location = 3) out vec2 fragTexCoord;

void main() {
    mat4 model = modelUBO.models[gl_InstanceIndex]; // Use the model matrix for the current instance
    gl_Position = projUBO.proj * viewUBO.view * model * vec4(inPosition, 1.0);
    fragPosition = vec3(model * vec4(inPosition, 1.0));
    fragNormal = normalize(mat3(transpose(inverse(model))) * inNormal);
    fragColor = inColor;
    fragTexCoord = inTexCoord;
}


Geometry Frag Shader:

#version 450

layout(location = 0) in vec3 fragPosition;
layout(location = 1) in vec3 fragNormal;
layout(location = 2) in vec3 fragColor;
layout(location = 3) in vec2 fragTexCoord;

layout(binding = 3) uniform sampler2D texSampler;

layout(set = 0, binding = 4) uniform Specular_Data {
    float intensity;
    float shininess;
} specular;

layout(location = 0) out vec4 gPosition;
layout(location = 1) out vec4 gNormal;
layout(location = 2) out vec4 gAlbedo;
layout(location = 3) out vec4 gSpecular;

void main() {
    gPosition = vec4(fragPosition, 1.0);
    gNormal = vec4(fragNormal, 1.0);
    gAlbedo = texture(texSampler, fragTexCoord);
    gSpecular = vec4(specular.intensity, specular.shininess, 1.0, 1.0);
}