| // |
| // Copyright 2016 The ANGLE Project Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| // ContextVk.cpp: |
| // Implements the class methods for ContextVk. |
| // |
| |
| #include "libANGLE/renderer/vulkan/ContextVk.h" |
| |
| #include "common/bitset_utils.h" |
| #include "common/debug.h" |
| #include "common/utilities.h" |
| #include "libANGLE/Context.h" |
| #include "libANGLE/Display.h" |
| #include "libANGLE/Program.h" |
| #include "libANGLE/Semaphore.h" |
| #include "libANGLE/Surface.h" |
| #include "libANGLE/angletypes.h" |
| #include "libANGLE/renderer/renderer_utils.h" |
| #include "libANGLE/renderer/vulkan/BufferVk.h" |
| #include "libANGLE/renderer/vulkan/CompilerVk.h" |
| #include "libANGLE/renderer/vulkan/DisplayVk.h" |
| #include "libANGLE/renderer/vulkan/FenceNVVk.h" |
| #include "libANGLE/renderer/vulkan/FramebufferVk.h" |
| #include "libANGLE/renderer/vulkan/MemoryObjectVk.h" |
| #include "libANGLE/renderer/vulkan/OverlayVk.h" |
| #include "libANGLE/renderer/vulkan/ProgramPipelineVk.h" |
| #include "libANGLE/renderer/vulkan/ProgramVk.h" |
| #include "libANGLE/renderer/vulkan/QueryVk.h" |
| #include "libANGLE/renderer/vulkan/RenderbufferVk.h" |
| #include "libANGLE/renderer/vulkan/RendererVk.h" |
| #include "libANGLE/renderer/vulkan/SamplerVk.h" |
| #include "libANGLE/renderer/vulkan/SemaphoreVk.h" |
| #include "libANGLE/renderer/vulkan/ShaderVk.h" |
| #include "libANGLE/renderer/vulkan/SurfaceVk.h" |
| #include "libANGLE/renderer/vulkan/SyncVk.h" |
| #include "libANGLE/renderer/vulkan/TextureVk.h" |
| #include "libANGLE/renderer/vulkan/TransformFeedbackVk.h" |
| #include "libANGLE/renderer/vulkan/VertexArrayVk.h" |
| |
| #include "libANGLE/trace.h" |
| |
| #include <iostream> |
| |
| namespace rx |
| { |
| |
| namespace |
| { |
| // For DesciptorSetUpdates |
| constexpr size_t kDescriptorBufferInfosInitialSize = 8; |
| constexpr size_t kDescriptorImageInfosInitialSize = 4; |
| constexpr size_t kDescriptorWriteInfosInitialSize = |
| kDescriptorBufferInfosInitialSize + kDescriptorImageInfosInitialSize; |
| |
| // For shader uniforms such as gl_DepthRange and the viewport size. |
| struct GraphicsDriverUniforms |
| { |
| std::array<float, 4> viewport; |
| |
| // 32 bits for 32 clip planes |
| uint32_t enabledClipPlanes; |
| |
| uint32_t xfbActiveUnpaused; |
| int32_t xfbVerticesPerInstance; |
| |
| // Used to replace gl_NumSamples. Because gl_NumSamples cannot be recognized in SPIR-V. |
| int32_t numSamples; |
| |
| std::array<int32_t, 4> xfbBufferOffsets; |
| |
| // .xy contain packed 8-bit values for atomic counter buffer offsets. These offsets are |
| // within Vulkan's minStorageBufferOffsetAlignment limit and are used to support unaligned |
| // offsets allowed in GL. |
| // |
| // .zw are unused. |
| std::array<uint32_t, 4> acbBufferOffsets; |
| |
| // We'll use x, y, z for near / far / diff respectively. |
| std::array<float, 4> depthRange; |
| }; |
| static_assert(sizeof(GraphicsDriverUniforms) % (sizeof(uint32_t) * 4) == 0, |
| "GraphicsDriverUniforms should 16bytes aligned"); |
| |
| // TODO: http://issuetracker.google.com/173636783 Once the bug is fixed, we should remove this. |
| struct GraphicsDriverUniformsExtended |
| { |
| GraphicsDriverUniforms common; |
| |
| // Used to flip gl_FragCoord (both .xy for Android pre-rotation; only .y for desktop) |
| std::array<float, 2> halfRenderArea; |
| std::array<float, 2> flipXY; |
| std::array<float, 2> negFlipXY; |
| std::array<int32_t, 2> padding; |
| |
| // Used to pre-rotate gl_FragCoord for swapchain images on Android (a mat2, which is padded to |
| // the size of two vec4's). |
| std::array<float, 8> fragRotation; |
| }; |
| |
| struct ComputeDriverUniforms |
| { |
| // Atomic counter buffer offsets with the same layout as in GraphicsDriverUniforms. |
| std::array<uint32_t, 4> acbBufferOffsets; |
| }; |
| |
| GLenum DefaultGLErrorCode(VkResult result) |
| { |
| switch (result) |
| { |
| case VK_ERROR_OUT_OF_HOST_MEMORY: |
| case VK_ERROR_OUT_OF_DEVICE_MEMORY: |
| case VK_ERROR_TOO_MANY_OBJECTS: |
| return GL_OUT_OF_MEMORY; |
| default: |
| return GL_INVALID_OPERATION; |
| } |
| } |
| |
| constexpr gl::ShaderMap<vk::ImageLayout> kShaderReadOnlyImageLayouts = { |
| {gl::ShaderType::Vertex, vk::ImageLayout::VertexShaderReadOnly}, |
| {gl::ShaderType::TessControl, vk::ImageLayout::PreFragmentShadersReadOnly}, |
| {gl::ShaderType::TessEvaluation, vk::ImageLayout::PreFragmentShadersReadOnly}, |
| {gl::ShaderType::Geometry, vk::ImageLayout::PreFragmentShadersReadOnly}, |
| {gl::ShaderType::Fragment, vk::ImageLayout::FragmentShaderReadOnly}, |
| {gl::ShaderType::Compute, vk::ImageLayout::ComputeShaderReadOnly}}; |
| |
| constexpr gl::ShaderMap<vk::ImageLayout> kShaderWriteImageLayouts = { |
| {gl::ShaderType::Vertex, vk::ImageLayout::VertexShaderWrite}, |
| {gl::ShaderType::TessControl, vk::ImageLayout::PreFragmentShadersWrite}, |
| {gl::ShaderType::TessEvaluation, vk::ImageLayout::PreFragmentShadersWrite}, |
| {gl::ShaderType::Geometry, vk::ImageLayout::PreFragmentShadersWrite}, |
| {gl::ShaderType::Fragment, vk::ImageLayout::FragmentShaderWrite}, |
| {gl::ShaderType::Compute, vk::ImageLayout::ComputeShaderWrite}}; |
| |
| constexpr VkBufferUsageFlags kVertexBufferUsage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; |
| constexpr size_t kDefaultValueSize = sizeof(gl::VertexAttribCurrentValueData::Values); |
| constexpr size_t kDefaultBufferSize = kDefaultValueSize * 16; |
| constexpr size_t kDriverUniformsAllocatorPageSize = 4 * 1024; |
| |
| uint32_t GetCoverageSampleCount(const gl::State &glState, FramebufferVk *drawFramebuffer) |
| { |
| if (!glState.isSampleCoverageEnabled()) |
| { |
| return 0; |
| } |
| |
| // Get a fraction of the samples based on the coverage parameters. |
| // There are multiple ways to obtain an integer value from a float - |
| // truncation, ceil and round |
| // |
| // round() provides a more even distribution of values but doesn't seem to play well |
| // with all vendors (AMD). A way to work around this is to increase the comparison threshold |
| // of deqp tests. Though this takes care of deqp tests other apps would still have issues. |
| // |
| // Truncation provides an uneven distribution near the edges of the interval but seems to |
| // play well with all vendors. |
| // |
| // We are going with truncation for expediency. |
| return static_cast<uint32_t>(glState.getSampleCoverageValue() * drawFramebuffer->getSamples()); |
| } |
| |
| void ApplySampleCoverage(const gl::State &glState, |
| uint32_t coverageSampleCount, |
| uint32_t maskNumber, |
| uint32_t *maskOut) |
| { |
| if (!glState.isSampleCoverageEnabled()) |
| { |
| return; |
| } |
| |
| uint32_t maskBitOffset = maskNumber * 32; |
| uint32_t coverageMask = coverageSampleCount >= (maskBitOffset + 32) |
| ? std::numeric_limits<uint32_t>::max() |
| : (1u << (coverageSampleCount - maskBitOffset)) - 1; |
| |
| if (glState.getSampleCoverageInvert()) |
| { |
| coverageMask = ~coverageMask; |
| } |
| |
| *maskOut &= coverageMask; |
| } |
| |
| bool IsRenderPassStartedAndUsesImage(const vk::CommandBufferHelper &renderPassCommands, |
| const vk::ImageHelper &image) |
| { |
| return renderPassCommands.started() && renderPassCommands.usesImageInRenderPass(image); |
| } |
| |
| // When an Android surface is rotated differently than the device's native orientation, ANGLE must |
| // rotate gl_Position in the last pre-rasterization shader and gl_FragCoord in the fragment shader. |
| // Rotation of gl_Position is done in SPIR-V. The following are the rotation matrices for the |
| // fragment shader. |
| // |
| // Note: these are mat2's that are appropriately padded (4 floats per row). |
| using PreRotationMatrixValues = std::array<float, 8>; |
| constexpr angle::PackedEnumMap<rx::SurfaceRotation, |
| PreRotationMatrixValues, |
| angle::EnumSize<rx::SurfaceRotation>()> |
| kFragRotationMatrices = { |
| {{rx::SurfaceRotation::Identity, {{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f}}}, |
| {rx::SurfaceRotation::Rotated90Degrees, |
| {{0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f}}}, |
| {rx::SurfaceRotation::Rotated180Degrees, |
| {{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f}}}, |
| {rx::SurfaceRotation::Rotated270Degrees, |
| {{0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f}}}, |
| {rx::SurfaceRotation::FlippedIdentity, {{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f}}}, |
| {rx::SurfaceRotation::FlippedRotated90Degrees, |
| {{0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f}}}, |
| {rx::SurfaceRotation::FlippedRotated180Degrees, |
| {{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f}}}, |
| {rx::SurfaceRotation::FlippedRotated270Degrees, |
| {{0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f}}}}}; |
| |
| bool IsRotatedAspectRatio(SurfaceRotation rotation) |
| { |
| return ((rotation == SurfaceRotation::Rotated90Degrees) || |
| (rotation == SurfaceRotation::Rotated270Degrees) || |
| (rotation == SurfaceRotation::FlippedRotated90Degrees) || |
| (rotation == SurfaceRotation::FlippedRotated270Degrees)); |
| } |
| |
| SurfaceRotation DetermineSurfaceRotation(gl::Framebuffer *framebuffer, |
| WindowSurfaceVk *windowSurface) |
| { |
| if (windowSurface && framebuffer->isDefault()) |
| { |
| switch (windowSurface->getPreTransform()) |
| { |
| case VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR: |
| // Do not rotate gl_Position (surface matches the device's orientation): |
| return SurfaceRotation::Identity; |
| case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: |
| // Rotate gl_Position 90 degrees: |
| return SurfaceRotation::Rotated90Degrees; |
| case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR: |
| // Rotate gl_Position 180 degrees: |
| return SurfaceRotation::Rotated180Degrees; |
| case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR: |
| // Rotate gl_Position 270 degrees: |
| return SurfaceRotation::Rotated270Degrees; |
| default: |
| UNREACHABLE(); |
| return SurfaceRotation::Identity; |
| } |
| } |
| else |
| { |
| // Do not rotate gl_Position (offscreen framebuffer): |
| return SurfaceRotation::Identity; |
| } |
| } |
| |
| // Should not generate a copy with modern C++. |
| EventName GetTraceEventName(const char *title, uint32_t counter) |
| { |
| EventName buf; |
| snprintf(buf.data(), kMaxGpuEventNameLen - 1, "%s %u", title, counter); |
| return buf; |
| } |
| |
| vk::ResourceAccess GetDepthAccess(const gl::DepthStencilState &dsState) |
| { |
| if (!dsState.depthTest) |
| { |
| return vk::ResourceAccess::Unused; |
| } |
| return dsState.isDepthMaskedOut() ? vk::ResourceAccess::ReadOnly : vk::ResourceAccess::Write; |
| } |
| |
| vk::ResourceAccess GetStencilAccess(const gl::DepthStencilState &dsState) |
| { |
| if (!dsState.stencilTest) |
| { |
| return vk::ResourceAccess::Unused; |
| } |
| |
| return dsState.isStencilNoOp() && dsState.isStencilBackNoOp() ? vk::ResourceAccess::ReadOnly |
| : vk::ResourceAccess::Write; |
| } |
| |
| egl::ContextPriority GetContextPriority(const gl::State &state) |
| { |
| return egl::FromEGLenum<egl::ContextPriority>(state.getContextPriority()); |
| } |
| } // anonymous namespace |
| |
| // Not necessary once upgraded to C++17. |
| constexpr ContextVk::DirtyBits ContextVk::kIndexAndVertexDirtyBits; |
| constexpr ContextVk::DirtyBits ContextVk::kPipelineDescAndBindingDirtyBits; |
| constexpr ContextVk::DirtyBits ContextVk::kTexturesAndDescSetDirtyBits; |
| constexpr ContextVk::DirtyBits ContextVk::kResourcesAndDescSetDirtyBits; |
| constexpr ContextVk::DirtyBits ContextVk::kXfbBuffersAndDescSetDirtyBits; |
| constexpr ContextVk::DirtyBits ContextVk::kDriverUniformsAndBindingDirtyBits; |
| |
| ANGLE_INLINE void ContextVk::flushDescriptorSetUpdates() |
| { |
| if (mWriteDescriptorSets.empty()) |
| { |
| ASSERT(mDescriptorBufferInfos.empty()); |
| ASSERT(mDescriptorImageInfos.empty()); |
| return; |
| } |
| |
| vkUpdateDescriptorSets(getDevice(), static_cast<uint32_t>(mWriteDescriptorSets.size()), |
| mWriteDescriptorSets.data(), 0, nullptr); |
| mWriteDescriptorSets.clear(); |
| mDescriptorBufferInfos.clear(); |
| mDescriptorImageInfos.clear(); |
| } |
| |
| ANGLE_INLINE void ContextVk::onRenderPassFinished() |
| { |
| pauseRenderPassQueriesIfActive(); |
| |
| mRenderPassCommandBuffer = nullptr; |
| mGraphicsDirtyBits.set(DIRTY_BIT_RENDER_PASS); |
| } |
| |
| // ContextVk::ScopedDescriptorSetUpdates implementation. |
| class ContextVk::ScopedDescriptorSetUpdates final : angle::NonCopyable |
| { |
| public: |
| ANGLE_INLINE ScopedDescriptorSetUpdates(ContextVk *contextVk) : mContextVk(contextVk) {} |
| ANGLE_INLINE ~ScopedDescriptorSetUpdates() { mContextVk->flushDescriptorSetUpdates(); } |
| |
| private: |
| ContextVk *mContextVk; |
| }; |
| |
| ContextVk::DriverUniformsDescriptorSet::DriverUniformsDescriptorSet() |
| : descriptorSet(VK_NULL_HANDLE), dynamicOffset(0) |
| {} |
| |
| ContextVk::DriverUniformsDescriptorSet::~DriverUniformsDescriptorSet() = default; |
| |
| void ContextVk::DriverUniformsDescriptorSet::init(RendererVk *rendererVk) |
| { |
| size_t minAlignment = static_cast<size_t>( |
| rendererVk->getPhysicalDeviceProperties().limits.minUniformBufferOffsetAlignment); |
| dynamicBuffer.init(rendererVk, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, minAlignment, |
| kDriverUniformsAllocatorPageSize, true, |
| vk::DynamicBufferPolicy::FrequentSmallAllocations); |
| descriptorSetCache.clear(); |
| } |
| |
| void ContextVk::DriverUniformsDescriptorSet::destroy(RendererVk *renderer) |
| { |
| descriptorSetLayout.reset(); |
| descriptorPoolBinding.reset(); |
| dynamicBuffer.destroy(renderer); |
| descriptorSetCache.clear(); |
| descriptorSetCache.destroy(renderer); |
| } |
| |
| // ContextVk implementation. |
| ContextVk::ContextVk(const gl::State &state, gl::ErrorSet *errorSet, RendererVk *renderer) |
| : ContextImpl(state, errorSet), |
| vk::Context(renderer), |
| mGraphicsDirtyBitHandlers{}, |
| mComputeDirtyBitHandlers{}, |
| mRenderPassCommandBuffer(nullptr), |
| mCurrentGraphicsPipeline(nullptr), |
| mCurrentComputePipeline(nullptr), |
| mCurrentDrawMode(gl::PrimitiveMode::InvalidEnum), |
| mCurrentWindowSurface(nullptr), |
| mCurrentRotationDrawFramebuffer(SurfaceRotation::Identity), |
| mCurrentRotationReadFramebuffer(SurfaceRotation::Identity), |
| mActiveRenderPassQueries{}, |
| mVertexArray(nullptr), |
| mDrawFramebuffer(nullptr), |
| mProgram(nullptr), |
| mExecutable(nullptr), |
| mLastIndexBufferOffset(nullptr), |
| mCurrentDrawElementsType(gl::DrawElementsType::InvalidEnum), |
| mXfbBaseVertex(0), |
| mXfbVertexCountPerInstance(0), |
| mClearColorValue{}, |
| mClearDepthStencilValue{}, |
| mClearColorMasks(0), |
| mFlipYForCurrentSurface(false), |
| mFlipViewportForDrawFramebuffer(false), |
| mFlipViewportForReadFramebuffer(false), |
| mIsAnyHostVisibleBufferWritten(false), |
| mEmulateSeamfulCubeMapSampling(false), |
| mOutsideRenderPassCommands(nullptr), |
| mRenderPassCommands(nullptr), |
| mGpuEventsEnabled(false), |
| mEGLSyncObjectPendingFlush(false), |
| mHasDeferredFlush(false), |
| mLastProgramUsesFramebufferFetch(false), |
| mGpuClockSync{std::numeric_limits<double>::max(), std::numeric_limits<double>::max()}, |
| mGpuEventTimestampOrigin(0), |
| mPerfCounters{}, |
| mContextPerfCounters{}, |
| mCumulativeContextPerfCounters{}, |
| mContextPriority(renderer->getDriverPriority(GetContextPriority(state))), |
| mShareGroupVk(vk::GetImpl(state.getShareGroup())) |
| { |
| ANGLE_TRACE_EVENT0("gpu.angle", "ContextVk::ContextVk"); |
| memset(&mClearColorValue, 0, sizeof(mClearColorValue)); |
| memset(&mClearDepthStencilValue, 0, sizeof(mClearDepthStencilValue)); |
| |
| mNonIndexedDirtyBitsMask.set(); |
| mNonIndexedDirtyBitsMask.reset(DIRTY_BIT_INDEX_BUFFER); |
| |
| mIndexedDirtyBitsMask.set(); |
| |
| // Once a command buffer is ended, all bindings (through |vkCmdBind*| calls) are lost per Vulkan |
| // spec. Once a new command buffer is allocated, we must make sure every previously bound |
| // resource is bound again. |
| // |
| // Note that currently these dirty bits are set every time a new render pass command buffer is |
| // begun. However, using ANGLE's SecondaryCommandBuffer, the Vulkan command buffer (which is |
| // the primary command buffer) is not ended, so technically we don't need to rebind these. |
| mNewGraphicsCommandBufferDirtyBits = DirtyBits{ |
| DIRTY_BIT_RENDER_PASS, DIRTY_BIT_PIPELINE_BINDING, DIRTY_BIT_TEXTURES, |
| DIRTY_BIT_VERTEX_BUFFERS, DIRTY_BIT_INDEX_BUFFER, DIRTY_BIT_SHADER_RESOURCES, |
| DIRTY_BIT_DESCRIPTOR_SETS, DIRTY_BIT_DRIVER_UNIFORMS_BINDING}; |
| if (getFeatures().supportsTransformFeedbackExtension.enabled) |
| { |
| mNewGraphicsCommandBufferDirtyBits.set(DIRTY_BIT_TRANSFORM_FEEDBACK_BUFFERS); |
| } |
| |
| mNewComputeCommandBufferDirtyBits = |
| DirtyBits{DIRTY_BIT_PIPELINE_BINDING, DIRTY_BIT_TEXTURES, DIRTY_BIT_SHADER_RESOURCES, |
| DIRTY_BIT_DESCRIPTOR_SETS, DIRTY_BIT_DRIVER_UNIFORMS_BINDING}; |
| |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_MEMORY_BARRIER] = |
| &ContextVk::handleDirtyGraphicsMemoryBarrier; |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_EVENT_LOG] = &ContextVk::handleDirtyGraphicsEventLog; |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_DEFAULT_ATTRIBS] = |
| &ContextVk::handleDirtyGraphicsDefaultAttribs; |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_PIPELINE_DESC] = |
| &ContextVk::handleDirtyGraphicsPipelineDesc; |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_RENDER_PASS] = &ContextVk::handleDirtyGraphicsRenderPass; |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_PIPELINE_BINDING] = |
| &ContextVk::handleDirtyGraphicsPipelineBinding; |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_TEXTURES] = &ContextVk::handleDirtyGraphicsTextures; |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_VERTEX_BUFFERS] = |
| &ContextVk::handleDirtyGraphicsVertexBuffers; |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_INDEX_BUFFER] = &ContextVk::handleDirtyGraphicsIndexBuffer; |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_DRIVER_UNIFORMS] = |
| &ContextVk::handleDirtyGraphicsDriverUniforms; |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_DRIVER_UNIFORMS_BINDING] = |
| &ContextVk::handleDirtyGraphicsDriverUniformsBinding; |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_SHADER_RESOURCES] = |
| &ContextVk::handleDirtyGraphicsShaderResources; |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_FRAMEBUFFER_FETCH_BARRIER] = |
| &ContextVk::handleDirtyGraphicsFramebufferFetchBarrier; |
| if (getFeatures().supportsTransformFeedbackExtension.enabled) |
| { |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_TRANSFORM_FEEDBACK_BUFFERS] = |
| &ContextVk::handleDirtyGraphicsTransformFeedbackBuffersExtension; |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_TRANSFORM_FEEDBACK_RESUME] = |
| &ContextVk::handleDirtyGraphicsTransformFeedbackResume; |
| } |
| else if (getFeatures().emulateTransformFeedback.enabled) |
| { |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_TRANSFORM_FEEDBACK_BUFFERS] = |
| &ContextVk::handleDirtyGraphicsTransformFeedbackBuffersEmulation; |
| } |
| |
| mGraphicsDirtyBitHandlers[DIRTY_BIT_DESCRIPTOR_SETS] = |
| &ContextVk::handleDirtyGraphicsDescriptorSets; |
| |
| mComputeDirtyBitHandlers[DIRTY_BIT_MEMORY_BARRIER] = |
| &ContextVk::handleDirtyComputeMemoryBarrier; |
| mComputeDirtyBitHandlers[DIRTY_BIT_EVENT_LOG] = &ContextVk::handleDirtyComputeEventLog; |
| mComputeDirtyBitHandlers[DIRTY_BIT_PIPELINE_DESC] = &ContextVk::handleDirtyComputePipelineDesc; |
| mComputeDirtyBitHandlers[DIRTY_BIT_PIPELINE_BINDING] = |
| &ContextVk::handleDirtyComputePipelineBinding; |
| mComputeDirtyBitHandlers[DIRTY_BIT_TEXTURES] = &ContextVk::handleDirtyComputeTextures; |
| mComputeDirtyBitHandlers[DIRTY_BIT_DRIVER_UNIFORMS] = |
| &ContextVk::handleDirtyComputeDriverUniforms; |
| mComputeDirtyBitHandlers[DIRTY_BIT_DRIVER_UNIFORMS_BINDING] = |
| &ContextVk::handleDirtyComputeDriverUniformsBinding; |
| mComputeDirtyBitHandlers[DIRTY_BIT_SHADER_RESOURCES] = |
| &ContextVk::handleDirtyComputeShaderResources; |
| mComputeDirtyBitHandlers[DIRTY_BIT_DESCRIPTOR_SETS] = |
| &ContextVk::handleDirtyComputeDescriptorSets; |
| |
| mGraphicsDirtyBits = mNewGraphicsCommandBufferDirtyBits; |
| mComputeDirtyBits = mNewComputeCommandBufferDirtyBits; |
| |
| mActiveTextures.fill({nullptr, nullptr, true}); |
| mActiveImages.fill(nullptr); |
| |
| // The following dirty bits don't affect the program pipeline: |
| // |
| // - READ_FRAMEBUFFER_BINDING only affects operations that read from said framebuffer, |
| // - CLEAR_* only affect following clear calls, |
| // - PACK/UNPACK_STATE only affect texture data upload/download, |
| // - *_BINDING only affect descriptor sets. |
| // |
| mPipelineDirtyBitsMask.set(); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_READ_FRAMEBUFFER_BINDING); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_CLEAR_COLOR); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_CLEAR_DEPTH); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_CLEAR_STENCIL); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_UNPACK_STATE); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_UNPACK_BUFFER_BINDING); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_PACK_STATE); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_PACK_BUFFER_BINDING); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_RENDERBUFFER_BINDING); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_DRAW_INDIRECT_BUFFER_BINDING); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_DISPATCH_INDIRECT_BUFFER_BINDING); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_SAMPLER_BINDINGS); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_TEXTURE_BINDINGS); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_IMAGE_BINDINGS); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_TRANSFORM_FEEDBACK_BINDING); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_UNIFORM_BUFFER_BINDINGS); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_SHADER_STORAGE_BUFFER_BINDING); |
| mPipelineDirtyBitsMask.reset(gl::State::DIRTY_BIT_ATOMIC_COUNTER_BUFFER_BINDING); |
| |
| // Reserve reasonable amount of spaces so that for majority of apps we don't need to grow at all |
| mDescriptorBufferInfos.reserve(kDescriptorBufferInfosInitialSize); |
| mDescriptorImageInfos.reserve(kDescriptorImageInfosInitialSize); |
| mWriteDescriptorSets.reserve(kDescriptorWriteInfosInitialSize); |
| } |
| |
| ContextVk::~ContextVk() = default; |
| |
| void ContextVk::onDestroy(const gl::Context *context) |
| { |
| outputCumulativePerfCounters(); |
| |
| // Remove context from the share group |
| mShareGroupVk->getShareContextSet()->erase(this); |
| |
| // This will not destroy any resources. It will release them to be collected after finish. |
| mIncompleteTextures.onDestroy(context); |
| |
| // Flush and complete current outstanding work before destruction. |
| (void)finishImpl(); |
| |
| VkDevice device = getDevice(); |
| |
| for (DriverUniformsDescriptorSet &driverUniforms : mDriverUniforms) |
| { |
| driverUniforms.destroy(mRenderer); |
| } |
| |
| for (vk::DynamicDescriptorPool &dynamicDescriptorPool : mDriverUniformsDescriptorPools) |
| { |
| dynamicDescriptorPool.destroy(device); |
| } |
| |
| mDefaultUniformStorage.release(mRenderer); |
| mEmptyBuffer.release(mRenderer); |
| mStagingBuffer.release(mRenderer); |
| |
| for (vk::DynamicBuffer &defaultBuffer : mDefaultAttribBuffers) |
| { |
| defaultBuffer.destroy(mRenderer); |
| } |
| |
| for (vk::DynamicQueryPool &queryPool : mQueryPools) |
| { |
| queryPool.destroy(device); |
| } |
| |
| // Recycle current commands buffers. |
| mRenderer->recycleCommandBufferHelper(mOutsideRenderPassCommands); |
| mRenderer->recycleCommandBufferHelper(mRenderPassCommands); |
| mOutsideRenderPassCommands = nullptr; |
| mRenderPassCommands = nullptr; |
| |
| mRenderer->releaseSharedResources(&mResourceUseList); |
| |
| mUtils.destroy(mRenderer); |
| |
| mRenderPassCache.destroy(mRenderer); |
| mShaderLibrary.destroy(device); |
| mGpuEventQueryPool.destroy(device); |
| mCommandPool.destroy(device); |
| |
| ASSERT(mCurrentGarbage.empty()); |
| ASSERT(mResourceUseList.empty()); |
| } |
| |
| angle::Result ContextVk::getIncompleteTexture(const gl::Context *context, |
| gl::TextureType type, |
| gl::SamplerFormat format, |
| gl::Texture **textureOut) |
| { |
| return mIncompleteTextures.getIncompleteTexture(context, type, format, this, textureOut); |
| } |
| |
| angle::Result ContextVk::initialize() |
| { |
| ANGLE_TRACE_EVENT0("gpu.angle", "ContextVk::initialize"); |
| |
| ANGLE_TRY(mQueryPools[gl::QueryType::AnySamples].init(this, VK_QUERY_TYPE_OCCLUSION, |
| vk::kDefaultOcclusionQueryPoolSize)); |
| ANGLE_TRY(mQueryPools[gl::QueryType::AnySamplesConservative].init( |
| this, VK_QUERY_TYPE_OCCLUSION, vk::kDefaultOcclusionQueryPoolSize)); |
| |
| // Only initialize the timestamp query pools if the extension is available. |
| if (mRenderer->getQueueFamilyProperties().timestampValidBits > 0) |
| { |
| ANGLE_TRY(mQueryPools[gl::QueryType::Timestamp].init(this, VK_QUERY_TYPE_TIMESTAMP, |
| vk::kDefaultTimestampQueryPoolSize)); |
| ANGLE_TRY(mQueryPools[gl::QueryType::TimeElapsed].init(this, VK_QUERY_TYPE_TIMESTAMP, |
| vk::kDefaultTimestampQueryPoolSize)); |
| } |
| |
| if (getFeatures().supportsTransformFeedbackExtension.enabled) |
| { |
| ANGLE_TRY(mQueryPools[gl::QueryType::TransformFeedbackPrimitivesWritten].init( |
| this, VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT, |
| vk::kDefaultTransformFeedbackQueryPoolSize)); |
| } |
| |
| // Init gles to vulkan index type map |
| initIndexTypeMap(); |
| |
| // Init driver uniforms and get the descriptor set layouts. |
| constexpr angle::PackedEnumMap<PipelineType, VkShaderStageFlags> kPipelineStages = { |
| {PipelineType::Graphics, VK_SHADER_STAGE_ALL_GRAPHICS}, |
| {PipelineType::Compute, VK_SHADER_STAGE_COMPUTE_BIT}, |
| }; |
| for (PipelineType pipeline : angle::AllEnums<PipelineType>()) |
| { |
| mDriverUniforms[pipeline].init(mRenderer); |
| |
| vk::DescriptorSetLayoutDesc desc = |
| getDriverUniformsDescriptorSetDesc(kPipelineStages[pipeline]); |
| ANGLE_TRY(getDescriptorSetLayoutCache().getDescriptorSetLayout( |
| this, desc, &mDriverUniforms[pipeline].descriptorSetLayout)); |
| |
| vk::DescriptorSetLayoutBindingVector bindingVector; |
| std::vector<VkSampler> immutableSamplers; |
| desc.unpackBindings(&bindingVector, &immutableSamplers); |
| std::vector<VkDescriptorPoolSize> descriptorPoolSizes; |
| |
| for (const VkDescriptorSetLayoutBinding &binding : bindingVector) |
| { |
| if (binding.descriptorCount > 0) |
| { |
| VkDescriptorPoolSize poolSize = {}; |
| |
| poolSize.type = binding.descriptorType; |
| poolSize.descriptorCount = binding.descriptorCount; |
| descriptorPoolSizes.emplace_back(poolSize); |
| } |
| } |
| if (!descriptorPoolSizes.empty()) |
| { |
| ANGLE_TRY(mDriverUniformsDescriptorPools[pipeline].init( |
| this, descriptorPoolSizes.data(), descriptorPoolSizes.size(), |
| mDriverUniforms[pipeline].descriptorSetLayout.get().getHandle())); |
| } |
| } |
| |
| mGraphicsPipelineDesc.reset(new vk::GraphicsPipelineDesc()); |
| mGraphicsPipelineDesc->initDefaults(this); |
| |
| // Initialize current value/default attribute buffers. |
| for (vk::DynamicBuffer &buffer : mDefaultAttribBuffers) |
| { |
| buffer.init(mRenderer, kVertexBufferUsage, 1, kDefaultBufferSize, true, |
| vk::DynamicBufferPolicy::FrequentSmallAllocations); |
| } |
| |
| #if ANGLE_ENABLE_VULKAN_GPU_TRACE_EVENTS |
| angle::PlatformMethods *platform = ANGLEPlatformCurrent(); |
| ASSERT(platform); |
| |
| // GPU tracing workaround for anglebug.com/2927. The renderer should not emit gpu events |
| // during platform discovery. |
| const unsigned char *gpuEventsEnabled = |
| platform->getTraceCategoryEnabledFlag(platform, "gpu.angle.gpu"); |
| mGpuEventsEnabled = gpuEventsEnabled && *gpuEventsEnabled; |
| #endif |
| |
| mEmulateSeamfulCubeMapSampling = shouldEmulateSeamfulCubeMapSampling(); |
| |
| // Assign initial command buffers from queue |
| mOutsideRenderPassCommands = mRenderer->getCommandBufferHelper(false); |
| mRenderPassCommands = mRenderer->getCommandBufferHelper(true); |
| |
| if (mGpuEventsEnabled) |
| { |
| // GPU events should only be available if timestamp queries are available. |
| ASSERT(mRenderer->getQueueFamilyProperties().timestampValidBits > 0); |
| // Calculate the difference between CPU and GPU clocks for GPU event reporting. |
| ANGLE_TRY(mGpuEventQueryPool.init(this, VK_QUERY_TYPE_TIMESTAMP, |
| vk::kDefaultTimestampQueryPoolSize)); |
| ANGLE_TRY(synchronizeCpuGpuTime()); |
| |
| mPerfCounters.primaryBuffers++; |
| |
| EventName eventName = GetTraceEventName("Primary", mPerfCounters.primaryBuffers); |
| ANGLE_TRY(traceGpuEvent(&mOutsideRenderPassCommands->getCommandBuffer(), |
| TRACE_EVENT_PHASE_BEGIN, eventName)); |
| } |
| |
| size_t minAlignment = static_cast<size_t>( |
| mRenderer->getPhysicalDeviceProperties().limits.minUniformBufferOffsetAlignment); |
| mDefaultUniformStorage.init(mRenderer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, minAlignment, |
| mRenderer->getDefaultUniformBufferSize(), true, |
| vk::DynamicBufferPolicy::FrequentSmallAllocations); |
| |
| // Initialize an "empty" buffer for use with default uniform blocks where there are no uniforms, |
| // or atomic counter buffer array indices that are unused. |
| constexpr VkBufferUsageFlags kEmptyBufferUsage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | |
| VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | |
| VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; |
| VkBufferCreateInfo emptyBufferInfo = {}; |
| emptyBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; |
| emptyBufferInfo.flags = 0; |
| emptyBufferInfo.size = 16; |
| emptyBufferInfo.usage = kEmptyBufferUsage; |
| emptyBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; |
| emptyBufferInfo.queueFamilyIndexCount = 0; |
| emptyBufferInfo.pQueueFamilyIndices = nullptr; |
| constexpr VkMemoryPropertyFlags kMemoryType = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; |
| ANGLE_TRY(mEmptyBuffer.init(this, emptyBufferInfo, kMemoryType)); |
| |
| constexpr VkImageUsageFlags kStagingBufferUsageFlags = |
| VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; |
| size_t stagingBufferAlignment = |
| static_cast<size_t>(mRenderer->getPhysicalDeviceProperties().limits.minMemoryMapAlignment); |
| constexpr size_t kStagingBufferSize = 1024u * 1024u; // 1M |
| mStagingBuffer.init(mRenderer, kStagingBufferUsageFlags, stagingBufferAlignment, |
| kStagingBufferSize, true, vk::DynamicBufferPolicy::SporadicTextureUpload); |
| |
| // Add context into the share group |
| mShareGroupVk->getShareContextSet()->insert(this); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::flush(const gl::Context *context) |
| { |
| // If a sync object has been used or this is a shared context, then we need to flush the |
| // commands and end the render pass to make sure the sync object (and any preceding commands) |
| // lands in the correct place within the command stream. |
| // EGL sync objects can span across context share groups, so don't defer flushes if there's one |
| // pending a flush. |
| if (getShareGroupVk()->isSyncObjectPendingFlush() && context->isShared() && |
| !mEGLSyncObjectPendingFlush) |
| { |
| // Flush the commands to create a sync point in the command stream. |
| ANGLE_TRY(flushCommandsAndEndRenderPass()); |
| // Move the resources to the share group, so they are released during the next vkQueueSubmit |
| // performed by any context in the share group. Note that this relies heavily on the global |
| // mutex to guarantee that no two contexts are modifying the lists at the same time. |
| getShareGroupVk()->acquireResourceUseList(std::move(mResourceUseList)); |
| mHasDeferredFlush = true; |
| return angle::Result::Continue; |
| } |
| |
| // EGL sync objects can span across context share groups, so don't defer flushes if there's one |
| // pending a flush. |
| if (!mEGLSyncObjectPendingFlush && |
| mRenderer->getFeatures().deferFlushUntilEndRenderPass.enabled && hasStartedRenderPass()) |
| { |
| mHasDeferredFlush = true; |
| return angle::Result::Continue; |
| } |
| |
| return flushImpl(nullptr); |
| } |
| |
| angle::Result ContextVk::finish(const gl::Context *context) |
| { |
| return finishImpl(); |
| } |
| |
| angle::Result ContextVk::setupDraw(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLint firstVertexOrInvalid, |
| GLsizei vertexOrIndexCount, |
| GLsizei instanceCount, |
| gl::DrawElementsType indexTypeOrInvalid, |
| const void *indices, |
| DirtyBits dirtyBitMask) |
| { |
| // Set any dirty bits that depend on draw call parameters or other objects. |
| if (mode != mCurrentDrawMode) |
| { |
| invalidateCurrentGraphicsPipeline(); |
| mCurrentDrawMode = mode; |
| mGraphicsPipelineDesc->updateTopology(&mGraphicsPipelineTransition, mCurrentDrawMode); |
| } |
| |
| // Must be called before the command buffer is started. Can call finish. |
| if (mVertexArray->getStreamingVertexAttribsMask().any()) |
| { |
| // All client attribs & any emulated buffered attribs will be updated |
| ANGLE_TRY(mVertexArray->updateStreamedAttribs(context, firstVertexOrInvalid, |
| vertexOrIndexCount, instanceCount, |
| indexTypeOrInvalid, indices)); |
| |
| mGraphicsDirtyBits.set(DIRTY_BIT_VERTEX_BUFFERS); |
| } |
| |
| // Create a local object to ensure we flush the descriptor updates to device when we leave this |
| // function |
| ScopedDescriptorSetUpdates descriptorSetUpdates(this); |
| |
| if (mProgram && mProgram->dirtyUniforms()) |
| { |
| ANGLE_TRY(mProgram->updateUniforms(this)); |
| mGraphicsDirtyBits.set(DIRTY_BIT_DESCRIPTOR_SETS); |
| } |
| else if (mProgramPipeline && mProgramPipeline->dirtyUniforms(getState())) |
| { |
| ANGLE_TRY(mProgramPipeline->updateUniforms(this)); |
| mGraphicsDirtyBits.set(DIRTY_BIT_DESCRIPTOR_SETS); |
| } |
| |
| // Update transform feedback offsets on every draw call when emulating transform feedback. This |
| // relies on the fact that no geometry/tessellation, indirect or indexed calls are supported in |
| // ES3.1 (and emulation is not done for ES3.2). |
| if (getFeatures().emulateTransformFeedback.enabled && |
| mState.isTransformFeedbackActiveUnpaused()) |
| { |
| ASSERT(firstVertexOrInvalid != -1); |
| mXfbBaseVertex = firstVertexOrInvalid; |
| mXfbVertexCountPerInstance = vertexOrIndexCount; |
| invalidateGraphicsDriverUniforms(); |
| } |
| |
| DirtyBits dirtyBits = mGraphicsDirtyBits & dirtyBitMask; |
| |
| if (dirtyBits.none()) |
| { |
| ASSERT(mRenderPassCommandBuffer); |
| return angle::Result::Continue; |
| } |
| |
| // Flush any relevant dirty bits. |
| for (DirtyBits::Iterator dirtyBitIter = dirtyBits.begin(); dirtyBitIter != dirtyBits.end(); |
| ++dirtyBitIter) |
| { |
| ASSERT(mGraphicsDirtyBitHandlers[*dirtyBitIter]); |
| ANGLE_TRY((this->*mGraphicsDirtyBitHandlers[*dirtyBitIter])(&dirtyBitIter, dirtyBitMask)); |
| } |
| |
| mGraphicsDirtyBits &= ~dirtyBitMask; |
| |
| // Render pass must be always available at this point. |
| ASSERT(mRenderPassCommandBuffer); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::setupIndexedDraw(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLsizei indexCount, |
| GLsizei instanceCount, |
| gl::DrawElementsType indexType, |
| const void *indices) |
| { |
| ASSERT(mode != gl::PrimitiveMode::LineLoop); |
| |
| if (indexType != mCurrentDrawElementsType) |
| { |
| mCurrentDrawElementsType = indexType; |
| ANGLE_TRY(onIndexBufferChange(nullptr)); |
| } |
| |
| const gl::Buffer *elementArrayBuffer = mVertexArray->getState().getElementArrayBuffer(); |
| if (!elementArrayBuffer) |
| { |
| mGraphicsDirtyBits.set(DIRTY_BIT_INDEX_BUFFER); |
| ANGLE_TRY(mVertexArray->convertIndexBufferCPU(this, indexType, indexCount, indices)); |
| } |
| else |
| { |
| if (indices != mLastIndexBufferOffset) |
| { |
| mGraphicsDirtyBits.set(DIRTY_BIT_INDEX_BUFFER); |
| mLastIndexBufferOffset = indices; |
| mVertexArray->updateCurrentElementArrayBufferOffset(mLastIndexBufferOffset); |
| } |
| if (shouldConvertUint8VkIndexType(indexType) && mGraphicsDirtyBits[DIRTY_BIT_INDEX_BUFFER]) |
| { |
| ANGLE_PERF_WARNING(getDebug(), GL_DEBUG_SEVERITY_LOW, |
| "Potential inefficiency emulating uint8 vertex attributes due to " |
| "lack of hardware support"); |
| |
| BufferVk *bufferVk = vk::GetImpl(elementArrayBuffer); |
| vk::BufferHelper &bufferHelper = bufferVk->getBuffer(); |
| |
| if (bufferHelper.isHostVisible() && |
| !bufferHelper.isCurrentlyInUse(getLastCompletedQueueSerial())) |
| { |
| uint8_t *src = nullptr; |
| ANGLE_TRY(bufferVk->mapImpl(this, reinterpret_cast<void **>(&src))); |
| src += reinterpret_cast<uintptr_t>(indices); |
| const size_t byteCount = static_cast<size_t>(elementArrayBuffer->getSize()) - |
| reinterpret_cast<uintptr_t>(indices); |
| ANGLE_TRY(mVertexArray->convertIndexBufferCPU(this, indexType, byteCount, src)); |
| ANGLE_TRY(bufferVk->unmapImpl(this)); |
| } |
| else |
| { |
| ANGLE_TRY(mVertexArray->convertIndexBufferGPU(this, bufferVk, indices)); |
| } |
| } |
| } |
| |
| return setupDraw(context, mode, 0, indexCount, instanceCount, indexType, indices, |
| mIndexedDirtyBitsMask); |
| } |
| |
| angle::Result ContextVk::setupIndirectDraw(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| DirtyBits dirtyBitMask, |
| vk::BufferHelper *indirectBuffer, |
| VkDeviceSize indirectBufferOffset) |
| { |
| GLint firstVertex = -1; |
| GLsizei vertexCount = 0; |
| GLsizei instanceCount = 1; |
| |
| // Break the render pass if the indirect buffer was previously used as the output from transform |
| // feedback. |
| if (mCurrentTransformFeedbackBuffers.contains(indirectBuffer)) |
| { |
| ANGLE_TRY(flushCommandsAndEndRenderPass()); |
| } |
| |
| ANGLE_TRY(setupDraw(context, mode, firstVertex, vertexCount, instanceCount, |
| gl::DrawElementsType::InvalidEnum, nullptr, dirtyBitMask)); |
| |
| // Process indirect buffer after render pass has started. |
| mRenderPassCommands->bufferRead(this, VK_ACCESS_INDIRECT_COMMAND_READ_BIT, |
| vk::PipelineStage::DrawIndirect, indirectBuffer); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::setupIndexedIndirectDraw(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| gl::DrawElementsType indexType, |
| vk::BufferHelper *indirectBuffer, |
| VkDeviceSize indirectBufferOffset) |
| { |
| ASSERT(mode != gl::PrimitiveMode::LineLoop); |
| |
| if (indexType != mCurrentDrawElementsType) |
| { |
| mCurrentDrawElementsType = indexType; |
| ANGLE_TRY(onIndexBufferChange(nullptr)); |
| } |
| |
| return setupIndirectDraw(context, mode, mIndexedDirtyBitsMask, indirectBuffer, |
| indirectBufferOffset); |
| } |
| |
| angle::Result ContextVk::setupLineLoopIndexedIndirectDraw(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| gl::DrawElementsType indexType, |
| vk::BufferHelper *srcIndirectBuf, |
| VkDeviceSize indirectBufferOffset, |
| vk::BufferHelper **indirectBufferOut, |
| VkDeviceSize *indirectBufferOffsetOut) |
| { |
| ASSERT(mode == gl::PrimitiveMode::LineLoop); |
| |
| vk::BufferHelper *dstIndirectBuf = nullptr; |
| VkDeviceSize dstIndirectBufOffset = 0; |
| |
| ANGLE_TRY(mVertexArray->handleLineLoopIndexIndirect(this, indexType, srcIndirectBuf, |
| indirectBufferOffset, &dstIndirectBuf, |
| &dstIndirectBufOffset)); |
| |
| *indirectBufferOut = dstIndirectBuf; |
| *indirectBufferOffsetOut = dstIndirectBufOffset; |
| |
| if (indexType != mCurrentDrawElementsType) |
| { |
| mCurrentDrawElementsType = indexType; |
| ANGLE_TRY(onIndexBufferChange(nullptr)); |
| } |
| |
| return setupIndirectDraw(context, mode, mIndexedDirtyBitsMask, dstIndirectBuf, |
| dstIndirectBufOffset); |
| } |
| |
| angle::Result ContextVk::setupLineLoopIndirectDraw(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| vk::BufferHelper *indirectBuffer, |
| VkDeviceSize indirectBufferOffset, |
| vk::BufferHelper **indirectBufferOut, |
| VkDeviceSize *indirectBufferOffsetOut) |
| { |
| ASSERT(mode == gl::PrimitiveMode::LineLoop); |
| |
| vk::BufferHelper *indirectBufferHelperOut = nullptr; |
| |
| ANGLE_TRY(mVertexArray->handleLineLoopIndirectDraw( |
| context, indirectBuffer, indirectBufferOffset, &indirectBufferHelperOut, |
| indirectBufferOffsetOut)); |
| |
| *indirectBufferOut = indirectBufferHelperOut; |
| |
| if (gl::DrawElementsType::UnsignedInt != mCurrentDrawElementsType) |
| { |
| mCurrentDrawElementsType = gl::DrawElementsType::UnsignedInt; |
| ANGLE_TRY(onIndexBufferChange(nullptr)); |
| } |
| |
| return setupIndirectDraw(context, mode, mIndexedDirtyBitsMask, indirectBufferHelperOut, |
| *indirectBufferOffsetOut); |
| } |
| |
| angle::Result ContextVk::setupLineLoopDraw(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLint firstVertex, |
| GLsizei vertexOrIndexCount, |
| gl::DrawElementsType indexTypeOrInvalid, |
| const void *indices, |
| uint32_t *numIndicesOut) |
| { |
| ANGLE_TRY(mVertexArray->handleLineLoop(this, firstVertex, vertexOrIndexCount, |
| indexTypeOrInvalid, indices, numIndicesOut)); |
| ANGLE_TRY(onIndexBufferChange(nullptr)); |
| mCurrentDrawElementsType = indexTypeOrInvalid != gl::DrawElementsType::InvalidEnum |
| ? indexTypeOrInvalid |
| : gl::DrawElementsType::UnsignedInt; |
| return setupDraw(context, mode, firstVertex, vertexOrIndexCount, 1, indexTypeOrInvalid, indices, |
| mIndexedDirtyBitsMask); |
| } |
| |
| angle::Result ContextVk::setupDispatch(const gl::Context *context) |
| { |
| // Note: numerous tests miss a glMemoryBarrier call between the initial texture data upload and |
| // the dispatch call. Flush the outside render pass command buffer as a workaround. |
| // TODO: Remove this and fix tests. http://anglebug.com/5070 |
| ANGLE_TRY(flushOutsideRenderPassCommands()); |
| |
| // Create a local object to ensure we flush the descriptor updates to device when we leave this |
| // function |
| ScopedDescriptorSetUpdates descriptorSetUpdates(this); |
| |
| if (mProgram && mProgram->dirtyUniforms()) |
| { |
| ANGLE_TRY(mProgram->updateUniforms(this)); |
| mComputeDirtyBits.set(DIRTY_BIT_DESCRIPTOR_SETS); |
| } |
| else if (mProgramPipeline && mProgramPipeline->dirtyUniforms(getState())) |
| { |
| ANGLE_TRY(mProgramPipeline->updateUniforms(this)); |
| mComputeDirtyBits.set(DIRTY_BIT_DESCRIPTOR_SETS); |
| } |
| |
| DirtyBits dirtyBits = mComputeDirtyBits; |
| |
| // Flush any relevant dirty bits. |
| for (size_t dirtyBit : dirtyBits) |
| { |
| ASSERT(mComputeDirtyBitHandlers[dirtyBit]); |
| ANGLE_TRY((this->*mComputeDirtyBitHandlers[dirtyBit])()); |
| } |
| |
| mComputeDirtyBits.reset(); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::handleDirtyGraphicsMemoryBarrier(DirtyBits::Iterator *dirtyBitsIterator, |
| DirtyBits dirtyBitMask) |
| { |
| return handleDirtyMemoryBarrierImpl(dirtyBitsIterator, dirtyBitMask); |
| } |
| |
| angle::Result ContextVk::handleDirtyComputeMemoryBarrier() |
| { |
| return handleDirtyMemoryBarrierImpl(nullptr, {}); |
| } |
| |
| bool ContextVk::renderPassUsesStorageResources() const |
| { |
| const gl::ProgramExecutable *executable = mState.getProgramExecutable(); |
| ASSERT(executable); |
| |
| // Storage images: |
| for (size_t imageUnitIndex : executable->getActiveImagesMask()) |
| { |
| const gl::Texture *texture = mState.getImageUnit(imageUnitIndex).texture.get(); |
| if (texture == nullptr) |
| { |
| continue; |
| } |
| |
| TextureVk *textureVk = vk::GetImpl(texture); |
| |
| if (texture->getType() == gl::TextureType::Buffer) |
| { |
| vk::BufferHelper &buffer = vk::GetImpl(textureVk->getBuffer().get())->getBuffer(); |
| if (mRenderPassCommands->usesBuffer(buffer)) |
| { |
| return true; |
| } |
| } |
| else |
| { |
| vk::ImageHelper &image = textureVk->getImage(); |
| // Images only need to close the render pass if they need a layout transition. Outside |
| // render pass command buffer doesn't need closing as the layout transition barriers are |
| // recorded in sequence with the rest of the commands. |
| if (IsRenderPassStartedAndUsesImage(*mRenderPassCommands, image)) |
| { |
| return true; |
| } |
| } |
| } |
| |
| gl::ShaderMap<const gl::ProgramState *> programStates; |
| mExecutable->fillProgramStateMap(this, &programStates); |
| |
| for (const gl::ShaderType shaderType : executable->getLinkedShaderStages()) |
| { |
| const gl::ProgramState *programState = programStates[shaderType]; |
| ASSERT(programState); |
| |
| // Storage buffers: |
| const std::vector<gl::InterfaceBlock> &blocks = programState->getShaderStorageBlocks(); |
| |
| for (uint32_t bufferIndex = 0; bufferIndex < blocks.size(); ++bufferIndex) |
| { |
| const gl::InterfaceBlock &block = blocks[bufferIndex]; |
| const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding = |
| mState.getIndexedShaderStorageBuffer(block.binding); |
| |
| if (!block.isActive(shaderType) || bufferBinding.get() == nullptr) |
| { |
| continue; |
| } |
| |
| vk::BufferHelper &buffer = vk::GetImpl(bufferBinding.get())->getBuffer(); |
| if (mRenderPassCommands->usesBuffer(buffer)) |
| { |
| return true; |
| } |
| } |
| |
| // Atomic counters: |
| const std::vector<gl::AtomicCounterBuffer> &atomicCounterBuffers = |
| programState->getAtomicCounterBuffers(); |
| |
| for (uint32_t bufferIndex = 0; bufferIndex < atomicCounterBuffers.size(); ++bufferIndex) |
| { |
| uint32_t binding = atomicCounterBuffers[bufferIndex].binding; |
| const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding = |
| mState.getIndexedAtomicCounterBuffer(binding); |
| |
| if (bufferBinding.get() == nullptr) |
| { |
| continue; |
| } |
| |
| vk::BufferHelper &buffer = vk::GetImpl(bufferBinding.get())->getBuffer(); |
| if (mRenderPassCommands->usesBuffer(buffer)) |
| { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| angle::Result ContextVk::handleDirtyMemoryBarrierImpl(DirtyBits::Iterator *dirtyBitsIterator, |
| DirtyBits dirtyBitMask) |
| { |
| const gl::ProgramExecutable *executable = mState.getProgramExecutable(); |
| ASSERT(executable); |
| |
| const bool hasImages = executable->hasImages(); |
| const bool hasStorageBuffers = executable->hasStorageBuffers(); |
| const bool hasAtomicCounters = executable->hasAtomicCounterBuffers(); |
| |
| if (!hasImages && !hasStorageBuffers && !hasAtomicCounters) |
| { |
| return angle::Result::Continue; |
| } |
| |
| // Break the render pass if necessary. This is only needed for write-after-read situations, and |
| // is done by checking whether current storage buffers and images are used in the render pass. |
| if (renderPassUsesStorageResources()) |
| { |
| // Either set later bits (if called during handling of graphics dirty bits), or set the |
| // dirty bits directly (if called during handling of compute dirty bits). |
| if (dirtyBitsIterator) |
| { |
| return flushDirtyGraphicsRenderPass(dirtyBitsIterator, dirtyBitMask); |
| } |
| else |
| { |
| return flushCommandsAndEndRenderPass(); |
| } |
| } |
| |
| // Flushing outside render pass commands is cheap. If a memory barrier has been issued in its |
| // life time, just flush it instead of wasting time trying to figure out if it's necessary. |
| if (mOutsideRenderPassCommands->hasGLMemoryBarrierIssued()) |
| { |
| ANGLE_TRY(flushOutsideRenderPassCommands()); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::handleDirtyGraphicsEventLog(DirtyBits::Iterator *dirtyBitsIterator, |
| DirtyBits dirtyBitMask) |
| { |
| return handleDirtyEventLogImpl(mRenderPassCommandBuffer); |
| } |
| |
| angle::Result ContextVk::handleDirtyComputeEventLog() |
| { |
| return handleDirtyEventLogImpl(&mOutsideRenderPassCommands->getCommandBuffer()); |
| } |
| |
| angle::Result ContextVk::handleDirtyEventLogImpl(vk::CommandBuffer *commandBuffer) |
| { |
| if (mEventLog.empty()) |
| { |
| return angle::Result::Continue; |
| } |
| |
| // Insert OpenGL ES commands into debug label. We create a 3-level cascade here for |
| // OpenGL-ES-first debugging in AGI. Here's the general outline of commands: |
| // -glDrawCommand |
| // --vkCmdBeginDebugUtilsLabelEXT() #1 for "glDrawCommand" |
| // --OpenGL ES Commands |
| // ---vkCmdBeginDebugUtilsLabelEXT() #2 for "OpenGL ES Commands" |
| // ---Individual OpenGL ES Commands leading up to glDrawCommand |
| // ----vkCmdBeginDebugUtilsLabelEXT() #3 for each individual OpenGL ES Command |
| // ----vkCmdEndDebugUtilsLabelEXT() #3 for each individual OpenGL ES Command |
| // ----...More Individual OGL Commands... |
| // ----Final Individual OGL command will be the same glDrawCommand shown in #1 above |
| // ---vkCmdEndDebugUtilsLabelEXT() #2 for "OpenGL ES Commands" |
| // --VK SetupDraw & Draw-related commands will be embedded here under glDraw #1 |
| // --vkCmdEndDebugUtilsLabelEXT() #1 is called after each vkDraw* or vkDispatch* call |
| VkDebugUtilsLabelEXT label = {VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, |
| nullptr, |
| mEventLog.back().c_str(), |
| {0.0f, 0.0f, 0.0f, 0.0f}}; |
| // This is #1 from comment above |
| commandBuffer->beginDebugUtilsLabelEXT(label); |
| std::string oglCmds = "OpenGL ES Commands"; |
| label.pLabelName = oglCmds.c_str(); |
| // This is #2 from comment above |
| commandBuffer->beginDebugUtilsLabelEXT(label); |
| for (uint32_t i = 0; i < mEventLog.size(); ++i) |
| { |
| label.pLabelName = mEventLog[i].c_str(); |
| // NOTE: We have to use a begin/end pair here because AGI does not promote the |
| // pLabelName from an insertDebugUtilsLabelEXT() call to the Commands panel. |
| // Internal bug b/169243237 is tracking this and once the insert* call shows the |
| // pLabelName similar to begin* call, we can switch these to insert* calls instead. |
| // This is #3 from comment above. |
| commandBuffer->beginDebugUtilsLabelEXT(label); |
| commandBuffer->endDebugUtilsLabelEXT(); |
| } |
| commandBuffer->endDebugUtilsLabelEXT(); |
| // The final end* call for #1 above is made in the ContextVk::draw* or |
| // ContextVk::dispatch* function calls. |
| |
| mEventLog.clear(); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::handleDirtyGraphicsDefaultAttribs(DirtyBits::Iterator *dirtyBitsIterator, |
| DirtyBits dirtyBitMask) |
| { |
| ASSERT(mDirtyDefaultAttribsMask.any()); |
| |
| for (size_t attribIndex : mDirtyDefaultAttribsMask) |
| { |
| ANGLE_TRY(updateDefaultAttribute(attribIndex)); |
| } |
| |
| mDirtyDefaultAttribsMask.reset(); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::handleDirtyGraphicsPipelineDesc(DirtyBits::Iterator *dirtyBitsIterator, |
| DirtyBits dirtyBitMask) |
| { |
| const VkPipeline previousPipeline = mCurrentGraphicsPipeline |
| ? mCurrentGraphicsPipeline->getPipeline().getHandle() |
| : VK_NULL_HANDLE; |
| |
| ASSERT(mExecutable); |
| |
| if (!mCurrentGraphicsPipeline) |
| { |
| const vk::GraphicsPipelineDesc *descPtr; |
| |
| // The desc's specialization constant depends on program's |
| // specConstUsageBits. We need to update it if program has changed. |
| SpecConstUsageBits usageBits = getCurrentProgramSpecConstUsageBits(); |
| updateGraphicsPipelineDescWithSpecConstUsageBits(usageBits); |
| |
| // Draw call shader patching, shader compilation, and pipeline cache query. |
| ANGLE_TRY(mExecutable->getGraphicsPipeline( |
| this, mCurrentDrawMode, *mGraphicsPipelineDesc, |
| mState.getProgramExecutable()->getNonBuiltinAttribLocationsMask(), &descPtr, |
| &mCurrentGraphicsPipeline)); |
| mGraphicsPipelineTransition.reset(); |
| } |
| else if (mGraphicsPipelineTransition.any()) |
| { |
| ASSERT(mCurrentGraphicsPipeline->valid()); |
| if (!mCurrentGraphicsPipeline->findTransition( |
| mGraphicsPipelineTransition, *mGraphicsPipelineDesc, &mCurrentGraphicsPipeline)) |
| { |
| vk::PipelineHelper *oldPipeline = mCurrentGraphicsPipeline; |
| const vk::GraphicsPipelineDesc *descPtr; |
| |
| ANGLE_TRY(mExecutable->getGraphicsPipeline( |
| this, mCurrentDrawMode, *mGraphicsPipelineDesc, |
| mState.getProgramExecutable()->getNonBuiltinAttribLocationsMask(), &descPtr, |
| &mCurrentGraphicsPipeline)); |
| |
| oldPipeline->addTransition(mGraphicsPipelineTransition, descPtr, |
| mCurrentGraphicsPipeline); |
| } |
| |
| mGraphicsPipelineTransition.reset(); |
| } |
| // Update the queue serial for the pipeline object. |
| ASSERT(mCurrentGraphicsPipeline && mCurrentGraphicsPipeline->valid()); |
| // TODO: https://issuetracker.google.com/issues/169788986: Need to change this so that we get |
| // the actual serial used when this work is submitted. |
| mCurrentGraphicsPipeline->updateSerial(getCurrentQueueSerial()); |
| |
| const VkPipeline newPipeline = mCurrentGraphicsPipeline->getPipeline().getHandle(); |
| |
| // If there's no change in pipeline, avoid rebinding it later. If the rebind is due to a new |
| // command buffer or UtilsVk, it will happen anyway with DIRTY_BIT_PIPELINE_BINDING. |
| if (newPipeline == previousPipeline) |
| { |
| return angle::Result::Continue; |
| } |
| |
| // VK_EXT_transform_feedback disallows binding pipelines while transform feedback is active. |
| // If a new pipeline needs to be bound, the render pass should necessarily be broken (which |
| // implicitly pauses transform feedback), as resuming requires a barrier on the transform |
| // feedback counter buffer. |
| if (mRenderPassCommands->started() && mRenderPassCommands->isTransformFeedbackActiveUnpaused()) |
| { |
| ANGLE_TRY(flushDirtyGraphicsRenderPass(dirtyBitsIterator, dirtyBitMask)); |
| |
| dirtyBitsIterator->setLaterBit(DIRTY_BIT_TRANSFORM_FEEDBACK_RESUME); |
| } |
| |
| // The pipeline needs to rebind because it's changed. |
| dirtyBitsIterator->setLaterBit(DIRTY_BIT_PIPELINE_BINDING); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::handleDirtyGraphicsRenderPass(DirtyBits::Iterator *dirtyBitsIterator, |
| DirtyBits dirtyBitMask) |
| { |
| // If the render pass needs to be recreated, close it using the special mid-dirty-bit-handling |
| // function, so later dirty bits can be set. |
| if (mRenderPassCommands->started()) |
| { |
| ANGLE_TRY(flushDirtyGraphicsRenderPass(dirtyBitsIterator, |
| dirtyBitMask & ~DirtyBits{DIRTY_BIT_RENDER_PASS})); |
| } |
| |
| gl::Rectangle scissoredRenderArea = mDrawFramebuffer->getRotatedScissoredRenderArea(this); |
| bool renderPassDescChanged = false; |
| |
| ANGLE_TRY(startRenderPass(scissoredRenderArea, nullptr, &renderPassDescChanged)); |
| |
| // The render pass desc can change when starting the render pass, for example due to |
| // multisampled-render-to-texture needs based on loadOps. In that case, recreate the graphics |
| // pipeline. |
| if (renderPassDescChanged) |
| { |
| ANGLE_TRY(handleDirtyGraphicsPipelineDesc(dirtyBitsIterator, dirtyBitMask)); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::handleDirtyGraphicsPipelineBinding(DirtyBits::Iterator *dirtyBitsIterator, |
| DirtyBits dirtyBitMask) |
| { |
| ASSERT(mCurrentGraphicsPipeline); |
| |
| mRenderPassCommandBuffer->bindGraphicsPipeline(mCurrentGraphicsPipeline->getPipeline()); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::handleDirtyComputePipelineDesc() |
| { |
| if (!mCurrentComputePipeline) |
| { |
| ASSERT(mExecutable); |
| ANGLE_TRY(mExecutable->getComputePipeline(this, &mCurrentComputePipeline)); |
| } |
| |
| ASSERT(mComputeDirtyBits.test(DIRTY_BIT_PIPELINE_BINDING)); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::handleDirtyComputePipelineBinding() |
| { |
| ASSERT(mCurrentComputePipeline); |
| |
| mOutsideRenderPassCommands->getCommandBuffer().bindComputePipeline( |
| mCurrentComputePipeline->get()); |
| // TODO: https://issuetracker.google.com/issues/169788986: Need to change this so that we get |
| // the actual serial used when this work is submitted. |
| mCurrentComputePipeline->updateSerial(getCurrentQueueSerial()); |
| |
| return angle::Result::Continue; |
| } |
| |
| ANGLE_INLINE angle::Result ContextVk::handleDirtyTexturesImpl( |
| vk::CommandBufferHelper *commandBufferHelper) |
| { |
| const gl::ProgramExecutable *executable = mState.getProgramExecutable(); |
| ASSERT(executable); |
| const gl::ActiveTextureMask &activeTextures = executable->getActiveSamplersMask(); |
| |
| for (size_t textureUnit : activeTextures) |
| { |
| const vk::TextureUnit &unit = mActiveTextures[textureUnit]; |
| TextureVk *textureVk = unit.texture; |
| |
| // If it's a texture buffer, get the attached buffer. |
| if (textureVk->getBuffer().get() != nullptr) |
| { |
| BufferVk *bufferVk = vk::GetImpl(textureVk->getBuffer().get()); |
| vk::BufferHelper &buffer = bufferVk->getBuffer(); |
| |
| gl::ShaderBitSet stages = |
| executable->getSamplerShaderBitsForTextureUnitIndex(textureUnit); |
| ASSERT(stages.any()); |
| |
| // TODO: accept multiple stages in bufferRead. http://anglebug.com/3573 |
| for (gl::ShaderType stage : stages) |
| { |
| // Note: if another range of the same buffer is simultaneously used for storage, |
| // such as for transform feedback output, or SSBO, unnecessary barriers can be |
| // generated. |
| commandBufferHelper->bufferRead(this, VK_ACCESS_SHADER_READ_BIT, |
| vk::GetPipelineStage(stage), &buffer); |
| } |
| |
| textureVk->retainBufferViews(&mResourceUseList); |
| |
| continue; |
| } |
| |
| vk::ImageHelper &image = textureVk->getImage(); |
| |
| // The image should be flushed and ready to use at this point. There may still be |
| // lingering staged updates in its staging buffer for unused texture mip levels or |
| // layers. Therefore we can't verify it has no staged updates right here. |
| |
| // Select the appropriate vk::ImageLayout depending on whether the texture is also bound as |
| // a GL image, and whether the program is a compute or graphics shader. |
| vk::ImageLayout textureLayout; |
| if (textureVk->hasBeenBoundAsImage()) |
| { |
| textureLayout = executable->isCompute() ? vk::ImageLayout::ComputeShaderWrite |
| : vk::ImageLayout::AllGraphicsShadersWrite; |
| } |
| else if (image.isDepthOrStencil()) |
| { |
| // We always use a depth-stencil read-only layout for any depth Textures to simplify our |
| // implementation's handling of depth-stencil read-only mode. We don't have to split a |
| // RenderPass to transition a depth texture from shader-read to read-only. This improves |
| // performance in Manhattan. Future optimizations are likely possible here including |
| // using specialized barriers without breaking the RenderPass. |
| textureLayout = vk::ImageLayout::DepthStencilReadOnly; |
| } |
| else |
| { |
| gl::ShaderBitSet remainingShaderBits = |
| executable->getSamplerShaderBitsForTextureUnitIndex(textureUnit); |
| ASSERT(remainingShaderBits.any()); |
| gl::ShaderType firstShader = remainingShaderBits.first(); |
| gl::ShaderType lastShader = remainingShaderBits.last(); |
| remainingShaderBits.reset(firstShader); |
| remainingShaderBits.reset(lastShader); |
| |
| if (image.hasRenderPassUseFlag(vk::RenderPassUsage::RenderTargetAttachment)) |
| { |
| // Right now we set this flag only when RenderTargetAttachment is set since we do |
| // not track all textures in the renderpass. |
| image.setRenderPassUsageFlag(vk::RenderPassUsage::TextureSampler); |
| |
| if (firstShader == gl::ShaderType::Fragment) |
| { |
| textureLayout = vk::ImageLayout::ColorAttachmentAndFragmentShaderRead; |
| } |
| else |
| { |
| textureLayout = vk::ImageLayout::ColorAttachmentAndAllShadersRead; |
| } |
| } |
| else |
| { |
| // We barrier against either: |
| // - Vertex only |
| // - Fragment only |
| // - Pre-fragment only (vertex, geometry and tessellation together) |
| if (remainingShaderBits.any() || firstShader != lastShader) |
| { |
| textureLayout = lastShader == gl::ShaderType::Fragment |
| ? vk::ImageLayout::AllGraphicsShadersReadOnly |
| : vk::ImageLayout::PreFragmentShadersReadOnly; |
| } |
| else |
| { |
| textureLayout = kShaderReadOnlyImageLayouts[firstShader]; |
| } |
| } |
| } |
| // Ensure the image is in the desired layout |
| commandBufferHelper->imageRead(this, image.getAspectFlags(), textureLayout, &image); |
| |
| textureVk->retainImageViews(&mResourceUseList); |
| } |
| |
| if (executable->hasTextures()) |
| { |
| ANGLE_TRY(mExecutable->updateTexturesDescriptorSet(this)); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::handleDirtyGraphicsTextures(DirtyBits::Iterator *dirtyBitsIterator, |
| DirtyBits dirtyBitMask) |
| { |
| return handleDirtyTexturesImpl(mRenderPassCommands); |
| } |
| |
| angle::Result ContextVk::handleDirtyComputeTextures() |
| { |
| return handleDirtyTexturesImpl(mOutsideRenderPassCommands); |
| } |
| |
| angle::Result ContextVk::handleDirtyGraphicsVertexBuffers(DirtyBits::Iterator *dirtyBitsIterator, |
| DirtyBits dirtyBitMask) |
| { |
| uint32_t maxAttrib = mState.getProgramExecutable()->getMaxActiveAttribLocation(); |
| const gl::AttribArray<VkBuffer> &bufferHandles = mVertexArray->getCurrentArrayBufferHandles(); |
| const gl::AttribArray<VkDeviceSize> &bufferOffsets = |
| mVertexArray->getCurrentArrayBufferOffsets(); |
| |
| mRenderPassCommandBuffer->bindVertexBuffers(0, maxAttrib, bufferHandles.data(), |
| bufferOffsets.data()); |
| |
| const gl::AttribArray<vk::BufferHelper *> &arrayBufferResources = |
| mVertexArray->getCurrentArrayBuffers(); |
| |
| // Mark all active vertex buffers as accessed. |
| const gl::ProgramExecutable *executable = mState.getProgramExecutable(); |
| gl::AttributesMask attribsMask = executable->getActiveAttribLocationsMask(); |
| for (size_t attribIndex : attribsMask) |
| { |
| vk::BufferHelper *arrayBuffer = arrayBufferResources[attribIndex]; |
| if (arrayBuffer) |
| { |
| mRenderPassCommands->bufferRead(this, VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, |
| vk::PipelineStage::VertexInput, arrayBuffer); |
| } |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::handleDirtyGraphicsIndexBuffer(DirtyBits::Iterator *dirtyBitsIterator, |
| DirtyBits dirtyBitMask) |
| { |
| vk::BufferHelper *elementArrayBuffer = mVertexArray->getCurrentElementArrayBuffer(); |
| ASSERT(elementArrayBuffer != nullptr); |
| |
| mRenderPassCommandBuffer->bindIndexBuffer(elementArrayBuffer->getBuffer(), |
| mVertexArray->getCurrentElementArrayBufferOffset(), |
| getVkIndexType(mCurrentDrawElementsType)); |
| |
| mRenderPassCommands->bufferRead(this, VK_ACCESS_INDEX_READ_BIT, vk::PipelineStage::VertexInput, |
| elementArrayBuffer); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::handleDirtyGraphicsFramebufferFetchBarrier( |
| DirtyBits::Iterator *dirtyBitsIterator, |
| DirtyBits dirtyBitMask) |
| { |
| VkMemoryBarrier memoryBarrier = {}; |
| memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; |
| memoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; |
| memoryBarrier.dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT; |
| |
| mRenderPassCommandBuffer->pipelineBarrier( |
| VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, |
| VK_DEPENDENCY_BY_REGION_BIT, 1, &memoryBarrier, 0, nullptr, 0, nullptr); |
| |
| return angle::Result::Continue; |
| } |
| |
| ANGLE_INLINE angle::Result ContextVk::handleDirtyShaderResourcesImpl( |
| vk::CommandBufferHelper *commandBufferHelper) |
| { |
| const gl::ProgramExecutable *executable = mState.getProgramExecutable(); |
| ASSERT(executable); |
| |
| const bool hasImages = executable->hasImages(); |
| const bool hasStorageBuffers = |
| executable->hasStorageBuffers() || executable->hasAtomicCounterBuffers(); |
| const bool hasUniformBuffers = executable->hasUniformBuffers(); |
| |
| if (hasImages) |
| { |
| ANGLE_TRY(updateActiveImages(commandBufferHelper)); |
| } |
| |
| if (hasUniformBuffers || hasStorageBuffers || hasImages || executable->usesFramebufferFetch()) |
| { |
| ANGLE_TRY(mExecutable->updateShaderResourcesDescriptorSet(this, mDrawFramebuffer, |
| commandBufferHelper)); |
| } |
| |
| // Record usage of storage buffers and images in the command buffer to aid handling of |
| // glMemoryBarrier. |
| if (hasImages || hasStorageBuffers) |
| { |
| commandBufferHelper->setHasShaderStorageOutput(); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::handleDirtyGraphicsShaderResources(DirtyBits::Iterator *dirtyBitsIterator, |
| DirtyBits dirtyBitMask) |
| { |
| return handleDirtyShaderResourcesImpl(mRenderPassCommands); |
| } |
| |
| angle::Result ContextVk::handleDirtyComputeShaderResources() |
| { |
| return handleDirtyShaderResourcesImpl(mOutsideRenderPassCommands); |
| } |
| |
| angle::Result ContextVk::handleDirtyGraphicsTransformFeedbackBuffersEmulation( |
| DirtyBits::Iterator *dirtyBitsIterator, |
| DirtyBits dirtyBitMask) |
| { |
| const gl::ProgramExecutable *executable = mState.getProgramExecutable(); |
| ASSERT(executable); |
| |
| if (!executable->hasTransformFeedbackOutput()) |
| { |
| return angle::Result::Continue; |
| } |
| |
| TransformFeedbackVk *transformFeedbackVk = vk::GetImpl(mState.getCurrentTransformFeedback()); |
| |
| if (mState.isTransformFeedbackActiveUnpaused()) |
| { |
| size_t bufferCount = executable->getTransformFeedbackBufferCount(); |
| const gl::TransformFeedbackBuffersArray<vk::BufferHelper *> &bufferHelpers = |
| transformFeedbackVk->getBufferHelpers(); |
| |
| for (size_t bufferIndex = 0; bufferIndex < bufferCount; ++bufferIndex) |
| { |
| vk::BufferHelper *bufferHelper = bufferHelpers[bufferIndex]; |
| ASSERT(bufferHelper); |
| mRenderPassCommands->bufferWrite(this, VK_ACCESS_SHADER_WRITE_BIT, |
| vk::PipelineStage::VertexShader, |
| vk::AliasingMode::Disallowed, bufferHelper); |
| } |
| } |
| |
| // TODO(http://anglebug.com/3570): Need to update to handle Program Pipelines |
| vk::BufferHelper *uniformBuffer = mDefaultUniformStorage.getCurrentBuffer(); |
| vk::UniformsAndXfbDescriptorDesc xfbBufferDesc = |
| transformFeedbackVk->getTransformFeedbackDesc(); |
| xfbBufferDesc.updateDefaultUniformBuffer(uniformBuffer ? uniformBuffer->getBufferSerial() |
| : vk::kInvalidBufferSerial); |
| |
| return mProgram->getExecutable().updateTransformFeedbackDescriptorSet( |
| mProgram->getState(), mProgram->getDefaultUniformBlocks(), uniformBuffer, this, |
| xfbBufferDesc); |
| } |
| |
| angle::Result ContextVk::handleDirtyGraphicsTransformFeedbackBuffersExtension( |
| DirtyBits::Iterator *dirtyBitsIterator, |
| DirtyBits dirtyBitMask) |
| { |
| const gl::ProgramExecutable *executable = mState.getProgramExecutable(); |
| ASSERT(executable); |
| |
| if (!executable->hasTransformFeedbackOutput() || !mState.isTransformFeedbackActive()) |
| { |
| return angle::Result::Continue; |
| } |
| |
| TransformFeedbackVk *transformFeedbackVk = vk::GetImpl(mState.getCurrentTransformFeedback()); |
| size_t bufferCount = executable->getTransformFeedbackBufferCount(); |
| |
| const gl::TransformFeedbackBuffersArray<vk::BufferHelper *> &buffers = |
| transformFeedbackVk->getBufferHelpers(); |
| gl::TransformFeedbackBuffersArray<vk::BufferHelper> &counterBuffers = |
| transformFeedbackVk->getCounterBufferHelpers(); |
| |
| // Issue necessary barriers for the transform feedback buffers. |
| for (size_t bufferIndex = 0; bufferIndex < bufferCount; ++bufferIndex) |
| { |
| vk::BufferHelper *bufferHelper = buffers[bufferIndex]; |
| ASSERT(bufferHelper); |
| mRenderPassCommands->bufferWrite(this, VK_ACCESS_TRANSFORM_FEEDBACK_WRITE_BIT_EXT, |
| vk::PipelineStage::TransformFeedback, |
| vk::AliasingMode::Disallowed, bufferHelper); |
| } |
| |
| // Issue necessary barriers for the transform feedback counter buffer. Note that the barrier is |
| // issued only on the first buffer (which uses a global memory barrier), as all the counter |
| // buffers of the transform feedback object are used together. |
| ASSERT(counterBuffers[0].valid()); |
| mRenderPassCommands->bufferWrite(this, |
| VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT | |
| VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_READ_BIT_EXT, |
| vk::PipelineStage::TransformFeedback, |
| vk::AliasingMode::Disallowed, &counterBuffers[0]); |
| |
| const gl::TransformFeedbackBuffersArray<VkBuffer> &bufferHandles = |
| transformFeedbackVk->getBufferHandles(); |
| const gl::TransformFeedbackBuffersArray<VkDeviceSize> &bufferOffsets = |
| transformFeedbackVk->getBufferOffsets(); |
| const gl::TransformFeedbackBuffersArray<VkDeviceSize> &bufferSizes = |
| transformFeedbackVk->getBufferSizes(); |
| |
| mRenderPassCommandBuffer->bindTransformFeedbackBuffers( |
| static_cast<uint32_t>(bufferCount), bufferHandles.data(), bufferOffsets.data(), |
| bufferSizes.data()); |
| |
| if (!mState.isTransformFeedbackActiveUnpaused()) |
| { |
| return angle::Result::Continue; |
| } |
| |
| // We should have same number of counter buffers as xfb buffers have |
| const gl::TransformFeedbackBuffersArray<VkBuffer> &counterBufferHandles = |
| transformFeedbackVk->getCounterBufferHandles(); |
| |
| bool rebindBuffers = transformFeedbackVk->getAndResetBufferRebindState(); |
| |
| mRenderPassCommands->beginTransformFeedback(bufferCount, counterBufferHandles.data(), |
| rebindBuffers); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::handleDirtyGraphicsTransformFeedbackResume( |
| DirtyBits::Iterator *dirtyBitsIterator, |
| DirtyBits dirtyBitMask) |
| { |
| if (mRenderPassCommands->isTransformFeedbackStarted()) |
| { |
| mRenderPassCommands->resumeTransformFeedback(); |
| } |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::handleDirtyGraphicsDescriptorSets(DirtyBits::Iterator *dirtyBitsIterator, |
| DirtyBits dirtyBitMask) |
| { |
| return handleDirtyDescriptorSetsImpl(mRenderPassCommandBuffer); |
| } |
| |
| angle::Result ContextVk::handleDirtyComputeDescriptorSets() |
| { |
| return handleDirtyDescriptorSetsImpl(&mOutsideRenderPassCommands->getCommandBuffer()); |
| } |
| |
| angle::Result ContextVk::handleDirtyDescriptorSetsImpl(vk::CommandBuffer *commandBuffer) |
| { |
| return mExecutable->updateDescriptorSets(this, commandBuffer); |
| } |
| |
| void ContextVk::syncObjectPerfCounters() |
| { |
| uint32_t descriptorSetAllocations = 0; |
| |
| // ContextVk's descriptor set allocations |
| ContextVkPerfCounters contextCounters = getAndResetObjectPerfCounters(); |
| for (uint32_t count : contextCounters.descriptorSetsAllocated) |
| { |
| descriptorSetAllocations += count; |
| } |
| // UtilsVk's descriptor set allocations |
| descriptorSetAllocations += mUtils.getAndResetObjectPerfCounters().descriptorSetsAllocated; |
| // ProgramExecutableVk's descriptor set allocations |
| const gl::State &state = getState(); |
| const gl::ShaderProgramManager &shadersAndPrograms = state.getShaderProgramManagerForCapture(); |
| const gl::ResourceMap<gl::Program, gl::ShaderProgramID> &programs = |
| shadersAndPrograms.getProgramsForCaptureAndPerf(); |
| for (const std::pair<GLuint, gl::Program *> &resource : programs) |
| { |
| ProgramVk *programVk = vk::GetImpl(resource.second); |
| ProgramExecutablePerfCounters progPerfCounters = |
| programVk->getExecutable().getAndResetObjectPerfCounters(); |
| |
| for (const uint32_t count : progPerfCounters.descriptorSetsAllocated) |
| { |
| descriptorSetAllocations += count; |
| } |
| } |
| mPerfCounters.descriptorSetAllocations = descriptorSetAllocations; |
| } |
| |
| void ContextVk::updateOverlayOnPresent() |
| { |
| const gl::OverlayType *overlay = mState.getOverlay(); |
| ASSERT(overlay->isEnabled()); |
| |
| syncObjectPerfCounters(); |
| |
| // Update overlay if active. |
| { |
| gl::RunningGraphWidget *renderPassCount = |
| overlay->getRunningGraphWidget(gl::WidgetId::VulkanRenderPassCount); |
| renderPassCount->add(mRenderPassCommands->getAndResetCounter()); |
| renderPassCount->next(); |
| } |
| |
| { |
| gl::RunningGraphWidget *writeDescriptorSetCount = |
| overlay->getRunningGraphWidget(gl::WidgetId::VulkanWriteDescriptorSetCount); |
| writeDescriptorSetCount->add(mPerfCounters.writeDescriptorSets); |
| writeDescriptorSetCount->next(); |
| |
| mPerfCounters.writeDescriptorSets = 0; |
| } |
| |
| { |
| gl::RunningGraphWidget *descriptorSetAllocationCount = |
| overlay->getRunningGraphWidget(gl::WidgetId::VulkanDescriptorSetAllocations); |
| descriptorSetAllocationCount->add(mPerfCounters.descriptorSetAllocations); |
| descriptorSetAllocationCount->next(); |
| } |
| |
| { |
| gl::RunningGraphWidget *dynamicBufferAllocations = |
| overlay->getRunningGraphWidget(gl::WidgetId::VulkanDynamicBufferAllocations); |
| dynamicBufferAllocations->next(); |
| } |
| } |
| |
| void ContextVk::addOverlayUsedBuffersCount(vk::CommandBufferHelper *commandBuffer) |
| { |
| const gl::OverlayType *overlay = mState.getOverlay(); |
| if (!overlay->isEnabled()) |
| { |
| return; |
| } |
| |
| gl::RunningHistogramWidget *widget = |
| overlay->getRunningHistogramWidget(gl::WidgetId::VulkanRenderPassBufferCount); |
| size_t buffersCount = commandBuffer->getUsedBuffersCount(); |
| if (buffersCount > 0) |
| { |
| widget->add(buffersCount); |
| widget->next(); |
| } |
| } |
| |
| angle::Result ContextVk::submitFrame(const vk::Semaphore *signalSemaphore) |
| { |
| if (mCurrentWindowSurface) |
| { |
| vk::Semaphore waitSemaphore = mCurrentWindowSurface->getAcquireImageSemaphore(); |
| if (waitSemaphore.valid()) |
| { |
| addWaitSemaphore(waitSemaphore.getHandle(), |
| VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); |
| addGarbage(&waitSemaphore); |
| } |
| } |
| |
| if (vk::CommandBufferHelper::kEnableCommandStreamDiagnostics) |
| { |
| dumpCommandStreamDiagnostics(); |
| } |
| |
| getShareGroupVk()->acquireResourceUseList(std::move(mResourceUseList)); |
| ANGLE_TRY(mRenderer->submitFrame(this, mContextPriority, std::move(mWaitSemaphores), |
| std::move(mWaitSemaphoreStageMasks), signalSemaphore, |
| getShareGroupVk()->releaseResourceUseLists(), |
| std::move(mCurrentGarbage), &mCommandPool)); |
| |
| onRenderPassFinished(); |
| mComputeDirtyBits |= mNewComputeCommandBufferDirtyBits; |
| |
| if (mGpuEventsEnabled) |
| { |
| ANGLE_TRY(checkCompletedGpuEvents()); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::synchronizeCpuGpuTime() |
| { |
| ASSERT(mGpuEventsEnabled); |
| |
| angle::PlatformMethods *platform = ANGLEPlatformCurrent(); |
| ASSERT(platform); |
| |
| // To synchronize CPU and GPU times, we need to get the CPU timestamp as close as possible |
| // to the GPU timestamp. The process of getting the GPU timestamp is as follows: |
| // |
| // CPU GPU |
| // |
| // Record command buffer |
| // with timestamp query |
| // |
| // Submit command buffer |
| // |
| // Post-submission work Begin execution |
| // |
| // ???? Write timestamp Tgpu |
| // |
| // ???? End execution |
| // |
| // ???? Return query results |
| // |
| // ???? |
| // |
| // Get query results |
| // |
| // The areas of unknown work (????) on the CPU indicate that the CPU may or may not have |
| // finished post-submission work while the GPU is executing in parallel. With no further |
| // work, querying CPU timestamps before submission and after getting query results give the |
| // bounds to Tgpu, which could be quite large. |
| // |
| // Using VkEvents, the GPU can be made to wait for the CPU and vice versa, in an effort to |
| // reduce this range. This function implements the following procedure: |
| // |
| // CPU GPU |
| // |
| // Record command buffer |
| // with timestamp query |
| // |
| // Submit command buffer |
| // |
| // Post-submission work Begin execution |
| // |
| // ???? Set Event GPUReady |
| // |
| // Wait on Event GPUReady Wait on Event CPUReady |
| // |
| // Get CPU Time Ts Wait on Event CPUReady |
| // |
| // Set Event CPUReady Wait on Event CPUReady |
| // |
| // Get CPU Time Tcpu Get GPU Time Tgpu |
| // |
| // Wait on Event GPUDone Set Event GPUDone |
| // |
| // Get CPU Time Te End Execution |
| // |
| // Idle Return query results |
| // |
| // Get query results |
| // |
| // If Te-Ts > epsilon, a GPU or CPU interruption can be assumed and the operation can be |
| // retried. Once Te-Ts < epsilon, Tcpu can be taken to presumably match Tgpu. Finding an |
| // epsilon that's valid for all devices may be difficult, so the loop can be performed only |
| // a limited number of times and the Tcpu,Tgpu pair corresponding to smallest Te-Ts used for |
| // calibration. |
| // |
| // Note: Once VK_EXT_calibrated_timestamps is ubiquitous, this should be redone. |
| |
| // Make sure nothing is running |
| ASSERT(!hasRecordedCommands()); |
| |
| ANGLE_TRACE_EVENT0("gpu.angle", "ContextVk::synchronizeCpuGpuTime"); |
| |
| // Create a query used to receive the GPU timestamp |
| vk::QueryHelper timestampQuery; |
| ANGLE_TRY(mGpuEventQueryPool.allocateQuery(this, ×tampQuery)); |
| |
| // Create the three events |
| VkEventCreateInfo eventCreateInfo = {}; |
| eventCreateInfo.sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO; |
| eventCreateInfo.flags = 0; |
| |
| VkDevice device = getDevice(); |
| vk::DeviceScoped<vk::Event> cpuReady(device), gpuReady(device), gpuDone(device); |
| ANGLE_VK_TRY(this, cpuReady.get().init(device, eventCreateInfo)); |
| ANGLE_VK_TRY(this, gpuReady.get().init(device, eventCreateInfo)); |
| ANGLE_VK_TRY(this, gpuDone.get().init(device, eventCreateInfo)); |
| |
| constexpr uint32_t kRetries = 10; |
| |
| // Time suffixes used are S for seconds and Cycles for cycles |
| double tightestRangeS = 1e6f; |
| double TcpuS = 0; |
| uint64_t TgpuCycles = 0; |
| for (uint32_t i = 0; i < kRetries; ++i) |
| { |
| // Reset the events |
| ANGLE_VK_TRY(this, cpuReady.get().reset(device)); |
| ANGLE_VK_TRY(this, gpuReady.get().reset(device)); |
| ANGLE_VK_TRY(this, gpuDone.get().reset(device)); |
| |
| // Record the command buffer |
| vk::DeviceScoped<vk::PrimaryCommandBuffer> commandBatch(device); |
| vk::PrimaryCommandBuffer &commandBuffer = commandBatch.get(); |
| |
| vk::ResourceUseList scratchResourceUseList; |
| |
| ANGLE_TRY(mRenderer->getCommandBufferOneOff(this, &commandBuffer)); |
| |
| commandBuffer.setEvent(gpuReady.get().getHandle(), VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT); |
| commandBuffer.waitEvents(1, cpuReady.get().ptr(), VK_PIPELINE_STAGE_HOST_BIT, |
| VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 0, nullptr, 0, nullptr, 0, |
| nullptr); |
| timestampQuery.writeTimestampToPrimary(this, &commandBuffer); |
| timestampQuery.retain(&scratchResourceUseList); |
| |
| commandBuffer.setEvent(gpuDone.get().getHandle(), VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT); |
| |
| ANGLE_VK_TRY(this, commandBuffer.end()); |
| |
| Serial throwAwaySerial; |
| // vkEvent's are externally synchronized, therefore need work to be submitted before calling |
| // vkGetEventStatus |
| ANGLE_TRY(mRenderer->queueSubmitOneOff(this, std::move(commandBuffer), mContextPriority, |
| nullptr, vk::SubmitPolicy::EnsureSubmitted, |
| &throwAwaySerial)); |
| scratchResourceUseList.releaseResourceUsesAndUpdateSerials(throwAwaySerial); |
| |
| // Wait for GPU to be ready. This is a short busy wait. |
| VkResult result = VK_EVENT_RESET; |
| do |
| { |
| result = gpuReady.get().getStatus(device); |
| if (result != VK_EVENT_SET && result != VK_EVENT_RESET) |
| { |
| ANGLE_VK_TRY(this, result); |
| } |
| } while (result == VK_EVENT_RESET); |
| |
| double TsS = platform->monotonicallyIncreasingTime(platform); |
| |
| // Tell the GPU to go ahead with the timestamp query. |
| ANGLE_VK_TRY(this, cpuReady.get().set(device)); |
| double cpuTimestampS = platform->monotonicallyIncreasingTime(platform); |
| |
| // Wait for GPU to be done. Another short busy wait. |
| do |
| { |
| result = gpuDone.get().getStatus(device); |
| if (result != VK_EVENT_SET && result != VK_EVENT_RESET) |
| { |
| ANGLE_VK_TRY(this, result); |
| } |
| } while (result == VK_EVENT_RESET); |
| |
| double TeS = platform->monotonicallyIncreasingTime(platform); |
| |
| // Get the query results |
| // Note: This LastSubmittedQueueSerial may include more work then was submitted above if |
| // another thread had submitted work. |
| ANGLE_TRY(finishToSerial(getLastSubmittedQueueSerial())); |
| |
| vk::QueryResult gpuTimestampCycles(1); |
| ANGLE_TRY(timestampQuery.getUint64Result(this, &gpuTimestampCycles)); |
| |
| // Use the first timestamp queried as origin. |
| if (mGpuEventTimestampOrigin == 0) |
| { |
| mGpuEventTimestampOrigin = |
| gpuTimestampCycles.getResult(vk::QueryResult::kDefaultResultIndex); |
| } |
| |
| // Take these CPU and GPU timestamps if there is better confidence. |
| double confidenceRangeS = TeS - TsS; |
| if (confidenceRangeS < tightestRangeS) |
| { |
| tightestRangeS = confidenceRangeS; |
| TcpuS = cpuTimestampS; |
| TgpuCycles = gpuTimestampCycles.getResult(vk::QueryResult::kDefaultResultIndex); |
| } |
| } |
| |
| mGpuEventQueryPool.freeQuery(this, ×tampQuery); |
| |
| // timestampPeriod gives nanoseconds/cycle. |
| double TgpuS = |
| (TgpuCycles - mGpuEventTimestampOrigin) * |
| static_cast<double>(getRenderer()->getPhysicalDeviceProperties().limits.timestampPeriod) / |
| 1'000'000'000.0; |
| |
| flushGpuEvents(TgpuS, TcpuS); |
| |
| mGpuClockSync.gpuTimestampS = TgpuS; |
| mGpuClockSync.cpuTimestampS = TcpuS; |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::traceGpuEventImpl(vk::CommandBuffer *commandBuffer, |
| char phase, |
| const EventName &name) |
| { |
| ASSERT(mGpuEventsEnabled); |
| |
| GpuEventQuery gpuEvent; |
| gpuEvent.name = name; |
| gpuEvent.phase = phase; |
| ANGLE_TRY(mGpuEventQueryPool.allocateQuery(this, &gpuEvent.queryHelper)); |
| |
| gpuEvent.queryHelper.writeTimestamp(this, commandBuffer); |
| |
| mInFlightGpuEventQueries.push_back(std::move(gpuEvent)); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::checkCompletedGpuEvents() |
| { |
| ASSERT(mGpuEventsEnabled); |
| |
| angle::PlatformMethods *platform = ANGLEPlatformCurrent(); |
| ASSERT(platform); |
| |
| int finishedCount = 0; |
| |
| Serial lastCompletedSerial = getLastCompletedQueueSerial(); |
| |
| for (GpuEventQuery &eventQuery : mInFlightGpuEventQueries) |
| { |
| // Only check the timestamp query if the submission has finished. |
| if (eventQuery.queryHelper.usedInRunningCommands(lastCompletedSerial)) |
| { |
| break; |
| } |
| |
| // See if the results are available. |
| vk::QueryResult gpuTimestampCycles(1); |
| bool available = false; |
| ANGLE_TRY(eventQuery.queryHelper.getUint64ResultNonBlocking(this, &gpuTimestampCycles, |
| &available)); |
| if (!available) |
| { |
| break; |
| } |
| |
| mGpuEventQueryPool.freeQuery(this, &eventQuery.queryHelper); |
| |
| GpuEvent gpuEvent; |
| gpuEvent.gpuTimestampCycles = |
| gpuTimestampCycles.getResult(vk::QueryResult::kDefaultResultIndex); |
| gpuEvent.name = eventQuery.name; |
| gpuEvent.phase = eventQuery.phase; |
| |
| mGpuEvents.emplace_back(gpuEvent); |
| |
| ++finishedCount; |
| } |
| |
| mInFlightGpuEventQueries.erase(mInFlightGpuEventQueries.begin(), |
| mInFlightGpuEventQueries.begin() + finishedCount); |
| |
| return angle::Result::Continue; |
| } |
| |
| void ContextVk::flushGpuEvents(double nextSyncGpuTimestampS, double nextSyncCpuTimestampS) |
| { |
| if (mGpuEvents.empty()) |
| { |
| return; |
| } |
| |
| angle::PlatformMethods *platform = ANGLEPlatformCurrent(); |
| ASSERT(platform); |
| |
| // Find the slope of the clock drift for adjustment |
| double lastGpuSyncTimeS = mGpuClockSync.gpuTimestampS; |
| double lastGpuSyncDiffS = mGpuClockSync.cpuTimestampS - mGpuClockSync.gpuTimestampS; |
| double gpuSyncDriftSlope = 0; |
| |
| double nextGpuSyncTimeS = nextSyncGpuTimestampS; |
| double nextGpuSyncDiffS = nextSyncCpuTimestampS - nextSyncGpuTimestampS; |
| |
| // No gpu trace events should have been generated before the clock sync, so if there is no |
| // "previous" clock sync, there should be no gpu events (i.e. the function early-outs |
| // above). |
| ASSERT(mGpuClockSync.gpuTimestampS != std::numeric_limits<double>::max() && |
| mGpuClockSync.cpuTimestampS != std::numeric_limits<double>::max()); |
| |
| gpuSyncDriftSlope = |
| (nextGpuSyncDiffS - lastGpuSyncDiffS) / (nextGpuSyncTimeS - lastGpuSyncTimeS); |
| |
| for (const GpuEvent &gpuEvent : mGpuEvents) |
| { |
| double gpuTimestampS = |
| (gpuEvent.gpuTimestampCycles - mGpuEventTimestampOrigin) * |
| static_cast<double>( |
| getRenderer()->getPhysicalDeviceProperties().limits.timestampPeriod) * |
| 1e-9; |
| |
| // Account for clock drift. |
| gpuTimestampS += lastGpuSyncDiffS + gpuSyncDriftSlope * (gpuTimestampS - lastGpuSyncTimeS); |
| |
| // Generate the trace now that the GPU timestamp is available and clock drifts are |
| // accounted for. |
| static long long eventId = 1; |
| static const unsigned char *categoryEnabled = |
| TRACE_EVENT_API_GET_CATEGORY_ENABLED(platform, "gpu.angle.gpu"); |
| platform->addTraceEvent(platform, gpuEvent.phase, categoryEnabled, gpuEvent.name.data(), |
| eventId++, gpuTimestampS, 0, nullptr, nullptr, nullptr, |
| TRACE_EVENT_FLAG_NONE); |
| } |
| |
| mGpuEvents.clear(); |
| } |
| |
| void ContextVk::clearAllGarbage() |
| { |
| ANGLE_TRACE_EVENT0("gpu.angle", "ContextVk::clearAllGarbage"); |
| for (vk::GarbageObject &garbage : mCurrentGarbage) |
| { |
| garbage.destroy(mRenderer); |
| } |
| mCurrentGarbage.clear(); |
| } |
| |
| void ContextVk::handleDeviceLost() |
| { |
| mOutsideRenderPassCommands->reset(); |
| mRenderPassCommands->reset(); |
| mRenderer->handleDeviceLost(); |
| clearAllGarbage(); |
| |
| mRenderer->notifyDeviceLost(); |
| } |
| |
| angle::Result ContextVk::drawArrays(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLint first, |
| GLsizei count) |
| { |
| uint32_t clampedVertexCount = gl::GetClampedVertexCount<uint32_t>(count); |
| |
| if (mode == gl::PrimitiveMode::LineLoop) |
| { |
| uint32_t numIndices; |
| ANGLE_TRY(setupLineLoopDraw(context, mode, first, count, gl::DrawElementsType::InvalidEnum, |
| nullptr, &numIndices)); |
| vk::LineLoopHelper::Draw(numIndices, 0, mRenderPassCommandBuffer); |
| } |
| else |
| { |
| ANGLE_TRY(setupDraw(context, mode, first, count, 1, gl::DrawElementsType::InvalidEnum, |
| nullptr, mNonIndexedDirtyBitsMask)); |
| mRenderPassCommandBuffer->draw(clampedVertexCount, first); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::drawArraysInstanced(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLint first, |
| GLsizei count, |
| GLsizei instances) |
| { |
| if (mode == gl::PrimitiveMode::LineLoop) |
| { |
| uint32_t clampedVertexCount = gl::GetClampedVertexCount<uint32_t>(count); |
| uint32_t numIndices; |
| ANGLE_TRY(setupLineLoopDraw(context, mode, first, clampedVertexCount, |
| gl::DrawElementsType::InvalidEnum, nullptr, &numIndices)); |
| mRenderPassCommandBuffer->drawIndexedInstanced(numIndices, instances); |
| return angle::Result::Continue; |
| } |
| |
| ANGLE_TRY(setupDraw(context, mode, first, count, instances, gl::DrawElementsType::InvalidEnum, |
| nullptr, mNonIndexedDirtyBitsMask)); |
| mRenderPassCommandBuffer->drawInstanced(gl::GetClampedVertexCount<uint32_t>(count), instances, |
| first); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::drawArraysInstancedBaseInstance(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLint first, |
| GLsizei count, |
| GLsizei instances, |
| GLuint baseInstance) |
| { |
| if (mode == gl::PrimitiveMode::LineLoop) |
| { |
| uint32_t clampedVertexCount = gl::GetClampedVertexCount<uint32_t>(count); |
| uint32_t numIndices; |
| ANGLE_TRY(setupLineLoopDraw(context, mode, first, clampedVertexCount, |
| gl::DrawElementsType::InvalidEnum, nullptr, &numIndices)); |
| mRenderPassCommandBuffer->drawIndexedInstancedBaseVertexBaseInstance(numIndices, instances, |
| 0, 0, baseInstance); |
| return angle::Result::Continue; |
| } |
| |
| ANGLE_TRY(setupDraw(context, mode, first, count, instances, gl::DrawElementsType::InvalidEnum, |
| nullptr, mNonIndexedDirtyBitsMask)); |
| mRenderPassCommandBuffer->drawInstancedBaseInstance(gl::GetClampedVertexCount<uint32_t>(count), |
| instances, first, baseInstance); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::drawElements(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices) |
| { |
| if (mode == gl::PrimitiveMode::LineLoop) |
| { |
| uint32_t indexCount; |
| ANGLE_TRY(setupLineLoopDraw(context, mode, 0, count, type, indices, &indexCount)); |
| vk::LineLoopHelper::Draw(indexCount, 0, mRenderPassCommandBuffer); |
| } |
| else |
| { |
| ANGLE_TRY(setupIndexedDraw(context, mode, count, 1, type, indices)); |
| mRenderPassCommandBuffer->drawIndexed(count); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::drawElementsBaseVertex(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices, |
| GLint baseVertex) |
| { |
| if (mode == gl::PrimitiveMode::LineLoop) |
| { |
| uint32_t indexCount; |
| ANGLE_TRY(setupLineLoopDraw(context, mode, 0, count, type, indices, &indexCount)); |
| vk::LineLoopHelper::Draw(indexCount, baseVertex, mRenderPassCommandBuffer); |
| } |
| else |
| { |
| ANGLE_TRY(setupIndexedDraw(context, mode, count, 1, type, indices)); |
| mRenderPassCommandBuffer->drawIndexedBaseVertex(count, baseVertex); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::drawElementsInstanced(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices, |
| GLsizei instances) |
| { |
| if (mode == gl::PrimitiveMode::LineLoop) |
| { |
| uint32_t indexCount; |
| ANGLE_TRY(setupLineLoopDraw(context, mode, 0, count, type, indices, &indexCount)); |
| count = indexCount; |
| } |
| else |
| { |
| ANGLE_TRY(setupIndexedDraw(context, mode, count, instances, type, indices)); |
| } |
| |
| mRenderPassCommandBuffer->drawIndexedInstanced(count, instances); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::drawElementsInstancedBaseVertex(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices, |
| GLsizei instances, |
| GLint baseVertex) |
| { |
| if (mode == gl::PrimitiveMode::LineLoop) |
| { |
| uint32_t indexCount; |
| ANGLE_TRY(setupLineLoopDraw(context, mode, 0, count, type, indices, &indexCount)); |
| count = indexCount; |
| } |
| else |
| { |
| ANGLE_TRY(setupIndexedDraw(context, mode, count, instances, type, indices)); |
| } |
| |
| mRenderPassCommandBuffer->drawIndexedInstancedBaseVertex(count, instances, baseVertex); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::drawElementsInstancedBaseVertexBaseInstance(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices, |
| GLsizei instances, |
| GLint baseVertex, |
| GLuint baseInstance) |
| { |
| if (mode == gl::PrimitiveMode::LineLoop) |
| { |
| uint32_t indexCount; |
| ANGLE_TRY(setupLineLoopDraw(context, mode, 0, count, type, indices, &indexCount)); |
| mRenderPassCommandBuffer->drawIndexedInstancedBaseVertexBaseInstance( |
| indexCount, instances, 0, baseVertex, baseInstance); |
| return angle::Result::Continue; |
| } |
| |
| ANGLE_TRY(setupIndexedDraw(context, mode, count, instances, type, indices)); |
| mRenderPassCommandBuffer->drawIndexedInstancedBaseVertexBaseInstance(count, instances, 0, |
| baseVertex, baseInstance); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::drawRangeElements(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLuint start, |
| GLuint end, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices) |
| { |
| return drawElements(context, mode, count, type, indices); |
| } |
| |
| angle::Result ContextVk::drawRangeElementsBaseVertex(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLuint start, |
| GLuint end, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices, |
| GLint baseVertex) |
| { |
| return drawElementsBaseVertex(context, mode, count, type, indices, baseVertex); |
| } |
| |
| VkDevice ContextVk::getDevice() const |
| { |
| return mRenderer->getDevice(); |
| } |
| |
| angle::Result ContextVk::drawArraysIndirect(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| const void *indirect) |
| { |
| gl::Buffer *indirectBuffer = mState.getTargetBuffer(gl::BufferBinding::DrawIndirect); |
| vk::BufferHelper *currentIndirectBuf = &vk::GetImpl(indirectBuffer)->getBuffer(); |
| VkDeviceSize currentIndirectBufOffset = reinterpret_cast<VkDeviceSize>(indirect); |
| |
| if (mVertexArray->getStreamingVertexAttribsMask().any()) |
| { |
| // We have instanced vertex attributes that need to be emulated for Vulkan. |
| // invalidate any cache and map the buffer so that we can read the indirect data. |
| // Mapping the buffer will cause a flush. |
| ANGLE_TRY(currentIndirectBuf->invalidate(mRenderer, 0, sizeof(VkDrawIndirectCommand))); |
| uint8_t *buffPtr; |
| ANGLE_TRY(currentIndirectBuf->map(this, &buffPtr)); |
| const VkDrawIndirectCommand *indirectData = |
| reinterpret_cast<VkDrawIndirectCommand *>(buffPtr + currentIndirectBufOffset); |
| |
| ANGLE_TRY(drawArraysInstanced(context, mode, indirectData->firstVertex, |
| indirectData->vertexCount, indirectData->instanceCount)); |
| |
| currentIndirectBuf->unmap(mRenderer); |
| return angle::Result::Continue; |
| } |
| |
| if (mode == gl::PrimitiveMode::LineLoop) |
| { |
| ASSERT(indirectBuffer); |
| vk::BufferHelper *dstIndirectBuf = nullptr; |
| VkDeviceSize dstIndirectBufOffset = 0; |
| |
| ANGLE_TRY(setupLineLoopIndirectDraw(context, mode, currentIndirectBuf, |
| currentIndirectBufOffset, &dstIndirectBuf, |
| &dstIndirectBufOffset)); |
| |
| mRenderPassCommandBuffer->drawIndexedIndirect(dstIndirectBuf->getBuffer(), |
| dstIndirectBufOffset, 1, 0); |
| return angle::Result::Continue; |
| } |
| |
| ANGLE_TRY(setupIndirectDraw(context, mode, mNonIndexedDirtyBitsMask, currentIndirectBuf, |
| currentIndirectBufOffset)); |
| |
| mRenderPassCommandBuffer->drawIndirect(currentIndirectBuf->getBuffer(), |
| currentIndirectBufOffset, 1, 0); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::drawElementsIndirect(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| gl::DrawElementsType type, |
| const void *indirect) |
| { |
| VkDeviceSize currentIndirectBufOffset = reinterpret_cast<VkDeviceSize>(indirect); |
| gl::Buffer *indirectBuffer = mState.getTargetBuffer(gl::BufferBinding::DrawIndirect); |
| ASSERT(indirectBuffer); |
| vk::BufferHelper *currentIndirectBuf = &vk::GetImpl(indirectBuffer)->getBuffer(); |
| |
| if (mVertexArray->getStreamingVertexAttribsMask().any()) |
| { |
| // We have instanced vertex attributes that need to be emulated for Vulkan. |
| // invalidate any cache and map the buffer so that we can read the indirect data. |
| // Mapping the buffer will cause a flush. |
| ANGLE_TRY( |
| currentIndirectBuf->invalidate(mRenderer, 0, sizeof(VkDrawIndexedIndirectCommand))); |
| uint8_t *buffPtr; |
| ANGLE_TRY(currentIndirectBuf->map(this, &buffPtr)); |
| const VkDrawIndexedIndirectCommand *indirectData = |
| reinterpret_cast<VkDrawIndexedIndirectCommand *>(buffPtr + currentIndirectBufOffset); |
| |
| ANGLE_TRY(drawElementsInstanced(context, mode, indirectData->indexCount, type, nullptr, |
| indirectData->instanceCount)); |
| |
| currentIndirectBuf->unmap(mRenderer); |
| return angle::Result::Continue; |
| } |
| |
| if (shouldConvertUint8VkIndexType(type) && mGraphicsDirtyBits[DIRTY_BIT_INDEX_BUFFER]) |
| { |
| ANGLE_PERF_WARNING(getDebug(), GL_DEBUG_SEVERITY_LOW, |
| "Potential inefficiency emulating uint8 vertex attributes due to lack " |
| "of hardware support"); |
| |
| vk::BufferHelper *dstIndirectBuf; |
| VkDeviceSize dstIndirectBufOffset; |
| |
| ANGLE_TRY(mVertexArray->convertIndexBufferIndirectGPU( |
| this, currentIndirectBuf, currentIndirectBufOffset, &dstIndirectBuf, |
| &dstIndirectBufOffset)); |
| |
| currentIndirectBuf = dstIndirectBuf; |
| currentIndirectBufOffset = dstIndirectBufOffset; |
| } |
| |
| if (mode == gl::PrimitiveMode::LineLoop) |
| { |
| vk::BufferHelper *dstIndirectBuf; |
| VkDeviceSize dstIndirectBufOffset; |
| |
| ANGLE_TRY(setupLineLoopIndexedIndirectDraw(context, mode, type, currentIndirectBuf, |
| currentIndirectBufOffset, &dstIndirectBuf, |
| &dstIndirectBufOffset)); |
| |
| currentIndirectBuf = dstIndirectBuf; |
| currentIndirectBufOffset = dstIndirectBufOffset; |
| } |
| else |
| { |
| ANGLE_TRY(setupIndexedIndirectDraw(context, mode, type, currentIndirectBuf, |
| currentIndirectBufOffset)); |
| } |
| |
| mRenderPassCommandBuffer->drawIndexedIndirect(currentIndirectBuf->getBuffer(), |
| currentIndirectBufOffset, 1, 0); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextVk::multiDrawArrays(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| const GLint *firsts, |
| const GLsizei *counts, |
| GLsizei drawcount) |
| { |
| return rx::MultiDrawArraysGeneral(this, context, mode, firsts, counts, drawcount); |
| } |
| |
| angle::Result ContextVk::multiDrawArraysInstanced(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| const GLint *firsts, |
| const GLsizei *counts, |
| const GLsizei *instanceCounts, |
| GLsizei drawcount) |
| { |
| return rx::MultiDrawArraysInstancedGeneral(this, context, mode, firsts, counts, instanceCounts, |
| drawcount); |
| } |
| |
| angle::Result ContextVk::multiDrawElements(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| const GLsizei *counts, |
| gl::DrawElementsType type, |
| const GLvoid *const *indices, |
| GLsizei drawcount) |
| { |
| return rx::MultiDrawElementsGeneral(this, context, mode, counts, type, indices, drawcount); |
| } |
| |
| angle::Result ContextVk::multiDrawElementsInstanced(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| const GLsizei *counts, |
| gl::DrawElementsType type, |
| const GLvoid *const *indices, |
| const GLsizei *instanceCounts, |
| GLsizei drawcount) |
| { |
| return rx::MultiDrawElementsInstancedGeneral(this, context, mode, counts, type, indices, |
| instanceCounts, drawcount); |
| } |
| |
| angle::Result ContextVk::multiDrawArraysInstancedBaseInstance(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| const GLint *firsts, |
| const GLsizei *counts, |
| const GLsizei *instanceCounts, |
| const GLuint *baseInstances, |
| GLsizei drawcount) |
| { |
| return rx::MultiDrawArraysInstancedBaseInstanceGeneral( |
| this, context, mode, firsts, counts, instanceCounts, baseInstances, drawcount); |
| } |
| |
| angle::Result ContextVk::multiDrawElementsInstancedBaseVertexBaseInstance( |
| const gl::Context *context, |
| gl::PrimitiveMode mode, |
| const GLsizei *counts, |
| gl::DrawElementsType type, |
| const GLvoid *const *indices, |
| const GLsizei *instanceCounts, |
| const GLint *baseVertices, |
|
|