Compare commits

..

1 Commits

Author SHA1 Message Date
Powei Feng
edb77fe6a9 docs: add doxygen file for DriverAPI.inc
This has been mostly auto-generated by an AI assistant. But
the documentation looks correct and can serve as a base point
for improvements.
2025-07-02 13:15:45 -07:00
17 changed files with 963 additions and 448 deletions

View File

@@ -0,0 +1,764 @@
/** @file DriverAPI.dox
* @brief External documentation for the backend::Driver API.
*
* This file contains Doxygen documentation for the functions declared in DriverAPI.inc.
* This is used to keep the documentation separate from the macro-heavy header file.
*/
// General lifecycle and frame management
/** @fn Driver::tick()
* @brief Called periodically by the system to perform maintenance tasks.
*/
/** @fn Driver::beginFrame(int64_t monotonic_clock_ns, int64_t refreshIntervalNs, uint32_t frameId)
* @brief Signals the beginning of a new frame to the driver.
* @param monotonic_clock_ns The current monotonic clock time in nanoseconds.
* @param refreshIntervalNs The display's refresh interval in nanoseconds.
* @param frameId A unique identifier for the new frame.
*/
/** @fn Driver::setFrameScheduledCallback(backend::SwapChainHandle sch, backend::CallbackHandler* handler, backend::FrameScheduledCallback&& callback, uint64_t flags)
* @brief Schedules a callback to be executed when the frame associated with the given swap chain has been scheduled for presentation.
* @param sch The handle of the swap chain.
* @param handler The callback handler that will execute the callback.
* @param callback The function to be called.
* @param flags Flags to control the callback behavior.
*/
/** @fn Driver::setFrameCompletedCallback(backend::SwapChainHandle sch, backend::CallbackHandler* handler, utils::Invocable<void(void)>&& callback)
* @brief Sets a callback to be executed when rendering to the given swap chain is complete for the current frame.
* @param sch The handle of the swap chain.
* @param handler The callback handler that will execute the callback.
* @param callback The function to be called upon frame completion.
*/
/** @fn Driver::setPresentationTime(int64_t monotonic_clock_ns)
* @brief Informs the driver of the intended presentation time for the current frame.
* @param monotonic_clock_ns The desired presentation time in nanoseconds, based on the monotonic clock.
*/
/** @fn Driver::endFrame(uint32_t frameId)
* @brief Signals the end of a frame to the driver.
* @param frameId The unique identifier of the frame that is ending.
*/
/** @fn Driver::flush()
* @brief Submits all pending commands to the GPU for execution, without waiting for them to complete.
*/
/** @fn Driver::finish()
* @brief Submits all pending commands and waits for the GPU to finish executing them.
*/
/** @fn Driver::resetState()
* @brief Resets any cached or tracked driver state, forcing the driver to re-evaluate and set all states.
*/
/** @fn Driver::setDebugTag(backend::HandleBase::HandleId handleId, utils::CString tag)
* @brief Associates a user-provided string tag with a driver handle for debugging purposes.
* @param handleId The ID of the handle to tag.
* @param tag The debug string to associate with the handle.
*/
// Resource creation
/** @fn backend::VertexBufferInfoHandle Driver::createVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount, backend::AttributeArray attributes)
* @brief Creates a VertexBufferInfo object which describes the layout of a vertex buffer.
* @param bufferCount The number of buffer objects in the vertex buffer.
* @param attributeCount The number of attributes in the vertex buffer.
* @param attributes An array describing each vertex attribute.
* @return A handle to the new VertexBufferInfo object.
*/
/** @fn backend::VertexBufferHandle Driver::createVertexBuffer(uint32_t vertexCount, backend::VertexBufferInfoHandle vbih)
* @brief Creates a vertex buffer with a specified vertex count and layout.
* @param vertexCount The number of vertices in the buffer.
* @param vbih A handle to a VertexBufferInfo object describing the vertex layout.
* @return A handle to the new vertex buffer.
*/
/** @fn backend::IndexBufferHandle Driver::createIndexBuffer(backend::ElementType elementType, uint32_t indexCount, backend::BufferUsage usage)
* @brief Creates an index buffer for indexed drawing.
* @param elementType The data type of an index (e.g., USHORT, UINT).
* @param indexCount The number of indices in the buffer.
* @param usage The expected usage pattern of the buffer (e.g., STATIC, DYNAMIC).
* @return A handle to the new index buffer.
*/
/** @fn backend::BufferObjectHandle Driver::createBufferObject(uint32_t byteCount, backend::BufferObjectBinding bindingType, backend::BufferUsage usage)
* @brief Creates a generic buffer object for storing arbitrary data on the GPU.
* @param byteCount The size of the buffer in bytes.
* @param bindingType The purpose of the buffer object (e.g., VERTEX, UNIFORM).
* @param usage The expected usage pattern of the buffer.
* @return A handle to the new buffer object.
*/
/** @fn backend::TextureHandle Driver::createTexture(backend::SamplerType target, uint8_t levels, backend::TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, backend::TextureUsage usage)
* @brief Creates a new texture object.
* @param target The type of the texture (e.g. 2D, 3D, CUBEMAP).
* @param levels The number of mipmap levels.
* @param format The internal format of the texture's pixels.
* @param samples The number of samples for multisampling (1 for non-MSAA).
* @param width The width of the texture in texels.
* @param height The height of the texture in texels.
* @param depth The depth of the texture in texels (for 3D or array textures).
* @param usage A bitmask specifying the intended usage of the texture.
* @return A handle to the new texture.
*/
/** @fn backend::TextureHandle Driver::createTextureView(backend::TextureHandle texture, uint8_t baseLevel, uint8_t levelCount)
* @brief Creates a new texture view from an existing texture, allowing access to a subset of its mipmap levels.
* @param texture The handle of the original texture.
* @param baseLevel The first mipmap level to include in the view.
* @param levelCount The number of mipmap levels to include in the view.
* @return A handle to the new texture view.
*/
/** @fn backend::TextureHandle Driver::createTextureViewSwizzle(backend::TextureHandle texture, backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b, backend::TextureSwizzle a)
* @brief Creates a new texture view with remapped color channels (swizzling).
* @param texture The handle of the original texture.
* @param r The swizzle operation for the red channel.
* @param g The swizzle operation for the green channel.
* @param b The swizzle operation for the blue channel.
* @param a The swizzle operation for the alpha channel.
* @return A handle to the new texture view.
*/
/** @fn backend::TextureHandle Driver::createTextureExternalImage2(backend::SamplerType target, backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, backend::Platform::ExternalImageHandleRef image)
* @brief Creates a texture from a platform-specific external image handle.
* @param target The type of the texture.
* @param format The format of the texture's pixels.
* @param width The width of the texture.
* @param height The height of the texture.
* @param usage The expected usage of the texture.
* @param image A reference to the platform-specific external image.
* @return A handle to the new texture.
*/
/** @fn backend::TextureHandle Driver::createTextureExternalImage(backend::SamplerType target, backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, void* image)
* @brief Creates a texture from a platform-specific external image pointer.
* @param target The type of the texture.
* @param format The format of the texture's pixels.
* @param width The width of the texture.
* @param height The height of the texture.
* @param usage The expected usage of the texture.
* @param image A pointer to the external image data.
* @return A handle to the new texture.
*/
/** @fn backend::TextureHandle Driver::createTextureExternalImagePlane(backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, void* image, uint32_t plane)
* @brief Creates a texture from a single plane of a multi-planar external image.
* @param format The format of the texture's pixels.
* @param width The width of the texture.
* @param height The height of the texture.
* @param usage The expected usage of the texture.
* @param image A pointer to the external image data.
* @param plane The index of the plane to create the texture from.
* @return A handle to the new texture.
*/
/** @fn backend::TextureHandle Driver::importTexture(intptr_t id, backend::SamplerType target, uint8_t levels, backend::TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, backend::TextureUsage usage)
* @brief Imports an existing backend-native texture into Filament.
* @param id The backend-specific texture ID or handle.
* @param target The type of the texture.
* @param levels The number of mipmap levels.
* @param format The format of the texture's pixels.
* @param samples The number of samples for multisampling.
* @param width The width of the texture.
* @param height The height of the texture.
* @param depth The depth of the texture.
* @param usage The expected usage of the texture.
* @return A handle to the imported texture.
*/
/** @fn backend::RenderPrimitiveHandle Driver::createRenderPrimitive(backend::VertexBufferHandle vbh, backend::IndexBufferHandle ibh, backend::PrimitiveType pt)
* @brief Creates a render primitive, which represents a piece of geometry to be rendered.
* @param vbh A handle to a vertex buffer.
* @param ibh A handle to an index buffer.
* @param pt The type of primitive to render (e.g., TRIANGLES, LINES).
* @return A handle to the new render primitive.
*/
/** @fn backend::ProgramHandle Driver::createProgram(backend::Program&& program)
* @brief Creates a shader program from a backend-specific program object.
* @param program A Program object containing the shader code and metadata.
* @return A handle to the new program.
*/
/** @fn backend::RenderTargetHandle Driver::createDefaultRenderTarget()
* @brief Creates a render target that represents the default framebuffer (e.g., the screen).
* @return A handle to the default render target.
*/
/** @fn backend::RenderTargetHandle Driver::createRenderTarget(backend::TargetBufferFlags targetBufferFlags, uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount, backend::MRT color, backend::TargetBufferInfo depth, backend::TargetBufferInfo stencil)
* @brief Creates an offscreen render target.
* @param targetBufferFlags A bitmask specifying which buffers (color, depth, stencil) are included.
* @param width The width of the render target in pixels.
* @param height The height of the render target in pixels.
* @param samples The number of samples for multisampling.
* @param layerCount The number of layers for layered rendering.
* @param color An array of color attachments.
* @param depth The depth attachment.
* @param stencil The stencil attachment.
* @return A handle to the new render target.
*/
/** @fn backend::FenceHandle Driver::createFence()
* @brief Creates a fence for synchronization between the CPU and GPU.
* @return A handle to the new fence.
*/
/** @fn backend::SwapChainHandle Driver::createSwapChain(void* nativeWindow, uint64_t flags)
* @brief Creates a swap chain for a native window, used for presenting rendered frames.
* @param nativeWindow A platform-specific pointer to the native window.
* @param flags Configuration flags for the swap chain.
* @return A handle to the new swap chain.
*/
/** @fn backend::SwapChainHandle Driver::createSwapChainHeadless(uint32_t width, uint32_t height, uint64_t flags)
* @brief Creates a headless swap chain for offscreen rendering without a native window.
* @param width The width of the swap chain.
* @param height The height of the swap chain.
* @param flags Configuration flags for the swap chain.
* @return A handle to the new swap chain.
*/
/** @fn backend::TimerQueryHandle Driver::createTimerQuery()
* @brief Creates a timer query object for measuring GPU execution time.
* @return A handle to the new timer query.
*/
/** @fn backend::DescriptorSetLayoutHandle Driver::createDescriptorSetLayout(backend::DescriptorSetLayout&& info)
* @brief Creates a descriptor set layout, which defines the layout of bindings in a descriptor set.
* @param info The layout information for the descriptor set.
* @return A handle to the new descriptor set layout.
*/
/** @fn backend::DescriptorSetHandle Driver::createDescriptorSet(backend::DescriptorSetLayoutHandle dslh)
* @brief Creates a descriptor set based on a given layout.
* @param dslh A handle to the descriptor set layout.
* @return A handle to the new descriptor set.
*/
/** @fn Driver::updateDescriptorSetBuffer(backend::DescriptorSetHandle dsh, backend::descriptor_binding_t binding, backend::BufferObjectHandle boh, uint32_t offset, uint32_t size)
* @brief Updates a buffer descriptor within a descriptor set.
* @param dsh The descriptor set to update.
* @param binding The binding point of the descriptor.
* @param boh The handle of the buffer object to bind.
* @param offset The offset into the buffer.
* @param size The size of the buffer region to bind.
*/
/** @fn Driver::updateDescriptorSetTexture(backend::DescriptorSetHandle dsh, backend::descriptor_binding_t binding, backend::TextureHandle th, SamplerParams params)
* @brief Updates a texture descriptor within a descriptor set.
* @param dsh The descriptor set to update.
* @param binding The binding point of the descriptor.
* @param th The handle of the texture to bind.
* @param params The sampler parameters for the texture.
*/
/** @fn Driver::bindDescriptorSet(backend::DescriptorSetHandle dsh, backend::descriptor_set_t set, backend::DescriptorSetOffsetArray&& offsets)
* @brief Binds a descriptor set to the current pipeline.
* @param dsh The descriptor set to bind.
* @param set The index of the descriptor set to bind.
* @param offsets An array of dynamic offsets for the descriptor set.
*/
// Resource destruction
/** @fn Driver::destroyVertexBuffer(backend::VertexBufferHandle vbh)
* @brief Destroys a vertex buffer and releases its resources.
* @param vbh The handle of the vertex buffer to destroy.
*/
/** @fn Driver::destroyVertexBufferInfo(backend::VertexBufferInfoHandle vbih)
* @brief Destroys a VertexBufferInfo object.
* @param vbih The handle of the VertexBufferInfo object to destroy.
*/
/** @fn Driver::destroyIndexBuffer(backend::IndexBufferHandle ibh)
* @brief Destroys an index buffer and releases its resources.
* @param ibh The handle of the index buffer to destroy.
*/
/** @fn Driver::destroyBufferObject(backend::BufferObjectHandle boh)
* @brief Destroys a buffer object and releases its resources.
* @param boh The handle of the buffer object to destroy.
*/
/** @fn Driver::destroyRenderPrimitive(backend::RenderPrimitiveHandle rph)
* @brief Destroys a render primitive.
* @param rph The handle of the render primitive to destroy.
*/
/** @fn Driver::destroyProgram(backend::ProgramHandle ph)
* @brief Destroys a shader program.
* @param ph The handle of the program to destroy.
*/
/** @fn Driver::destroyTexture(backend::TextureHandle th)
* @brief Destroys a texture and releases its resources.
* @param th The handle of the texture to destroy.
*/
/** @fn Driver::destroyRenderTarget(backend::RenderTargetHandle rth)
* @brief Destroys a render target.
* @param rth The handle of the render target to destroy.
*/
/** @fn Driver::destroySwapChain(backend::SwapChainHandle sch)
* @brief Destroys a swap chain.
* @param sch The handle of the swap chain to destroy.
*/
/** @fn Driver::destroyStream(backend::StreamHandle sh)
* @brief Destroys a stream.
* @param sh The handle of the stream to destroy.
*/
/** @fn Driver::destroyTimerQuery(backend::TimerQueryHandle tqh)
* @brief Destroys a timer query object.
* @param tqh The handle of the timer query to destroy.
*/
/** @fn Driver::destroyFence(backend::FenceHandle fh)
* @brief Destroys a fence.
* @param fh The handle of the fence to destroy.
*/
/** @fn Driver::destroyDescriptorSetLayout(backend::DescriptorSetLayoutHandle dslh)
* @brief Destroys a descriptor set layout.
* @param dslh The handle of the descriptor set layout to destroy.
*/
/** @fn Driver::destroyDescriptorSet(backend::DescriptorSetHandle dsh)
* @brief Destroys a descriptor set.
* @param dsh The handle of the descriptor set to destroy.
*/
// Synchronous APIs
/** @fn void Driver::terminate()
* @brief Terminates the driver and releases all associated resources. This is a synchronous operation.
*/
/** @fn backend::StreamHandle Driver::createStreamNative(void* stream)
* @brief Creates a stream from a native stream object (e.g., a SurfaceTexture on Android).
* @param stream A pointer to the native stream object.
* @return A handle to the new stream.
*/
/** @fn backend::StreamHandle Driver::createStreamAcquired()
* @brief Creates a stream that will be populated with images acquired via `setAcquiredImage`.
* @return A handle to the new stream.
*/
/** @fn void Driver::setAcquiredImage(backend::StreamHandle stream, void* image, const math::mat3f& transform, backend::CallbackHandler* handler, backend::StreamCallback cb, void* userData)
* @brief Provides an image to an acquired stream, with a callback for when the image is no longer in use.
* @param stream The handle of the stream.
* @param image A pointer to the image data.
* @param transform The transformation matrix for the image.
* @param handler The callback handler.
* @param cb The callback function to be invoked when the image is released.
* @param userData User data for the callback.
*/
/** @fn void Driver::setStreamDimensions(backend::StreamHandle stream, uint32_t width, uint32_t height)
* @brief Sets the dimensions of a stream, which may be necessary for certain stream types.
* @param stream The handle of the stream.
* @param width The new width of the stream.
* @param height The new height of the stream.
*/
/** @fn int64_t Driver::getStreamTimestamp(backend::StreamHandle stream)
* @brief Gets the timestamp of the last frame from a stream, if available.
* @param stream The handle of the stream.
* @return The timestamp in nanoseconds, or 0 if not available.
*/
/** @fn void Driver::updateStreams(backend::DriverApi* driver)
* @brief Updates all active streams, typically called once per frame.
* @param driver The driver API pointer.
*/
/** @fn backend::FenceStatus Driver::getFenceStatus(backend::FenceHandle fh)
* @brief Gets the current status of a fence.
* @param fh The handle of the fence.
* @return The status of the fence (e.g., SIGNALED, TIMEOUT_EXPIRED).
*/
/** @fn bool Driver::isTextureFormatSupported(backend::TextureFormat format)
* @brief Checks if a specific texture format is supported by the driver.
* @param format The texture format to check.
* @return True if the format is supported, false otherwise.
*/
/** @fn bool Driver::isTextureSwizzleSupported()
* @brief Checks if texture channel swizzling is supported by the driver.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isTextureFormatMipmappable(backend::TextureFormat format)
* @brief Checks if a texture format can have mipmaps automatically generated.
* @param format The texture format to check.
* @return True if the format is mipmappable, false otherwise.
*/
/** @fn bool Driver::isRenderTargetFormatSupported(backend::TextureFormat format)
* @brief Checks if a texture format is supported for use as a render target attachment.
* @param format The texture format to check.
* @return True if the format is supported, false otherwise.
*/
/** @fn bool Driver::isFrameBufferFetchSupported()
* @brief Checks if framebuffer fetch (reading from the framebuffer in a shader) is supported.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isFrameBufferFetchMultiSampleSupported()
* @brief Checks if multisampled framebuffer fetch is supported.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isFrameTimeSupported()
* @brief Checks if frame time queries are supported for performance measurement.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isAutoDepthResolveSupported()
* @brief Checks if automatic resolution of multisampled depth buffers is supported.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isSRGBSwapChainSupported()
* @brief Checks if sRGB swap chains are supported for correct color space handling.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isProtectedContentSupported()
* @brief Checks if rendering protected content (e.g., for DRM) is supported.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isStereoSupported()
* @brief Checks if stereoscopic rendering is supported.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isParallelShaderCompileSupported()
* @brief Checks if the driver can compile shaders in parallel for improved performance.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isDepthStencilResolveSupported()
* @brief Checks if resolving multisampled depth/stencil buffers is supported.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isDepthStencilBlitSupported(backend::TextureFormat format)
* @brief Checks if blitting (copying) depth/stencil data is supported for a given format.
* @param format The texture format.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isProtectedTexturesSupported()
* @brief Checks if creating protected textures is supported.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isDepthClampSupported()
* @brief Checks if depth clamping is supported by the hardware.
* @return True if supported, false otherwise.
*/
/** @fn uint8_t Driver::getMaxDrawBuffers()
* @brief Gets the maximum number of simultaneous draw buffers (Multiple Render Targets).
* @return The maximum number of draw buffers.
*/
/** @fn size_t Driver::getMaxUniformBufferSize()
* @brief Gets the maximum size of a uniform buffer in bytes.
* @return The maximum size in bytes.
*/
/** @fn size_t Driver::getMaxTextureSize(backend::SamplerType target)
* @brief Gets the maximum texture dimension (width, height, or depth) for a given target type.
* @param target The texture target type.
* @return The maximum size in texels.
*/
/** @fn size_t Driver::getMaxArrayTextureLayers()
* @brief Gets the maximum number of layers in an array texture.
* @return The maximum number of layers.
*/
/** @fn math::float2 Driver::getClipSpaceParams()
* @brief Gets the clip space parameters for the current backend.
* @return A float2 containing clip space parameters.
*/
/** @fn void Driver::setupExternalImage2(backend::Platform::ExternalImageHandleRef image)
* @brief Performs any necessary setup for an external image before it can be used.
* @param image A reference to the external image.
*/
/** @fn void Driver::setupExternalImage(void* image)
* @brief Performs any necessary setup for an external image before it can be used.
* @param image A pointer to the external image.
*/
/** @fn backend::TimerQueryResult Driver::getTimerQueryValue(backend::TimerQueryHandle query, uint64_t* elapsedTime)
* @brief Gets the result of a timer query, providing the elapsed GPU time.
* @param query The timer query handle.
* @param elapsedTime A pointer to store the elapsed time in nanoseconds.
* @return The result of the query (e.g., AVAILABLE, NOT_READY).
*/
/** @fn bool Driver::isWorkaroundNeeded(backend::Workaround workaround)
* @brief Checks if a specific driver or hardware workaround is needed.
* @param workaround The workaround to check.
* @return True if the workaround is needed, false otherwise.
*/
/** @fn backend::FeatureLevel Driver::getFeatureLevel()
* @brief Gets the feature level supported by the driver.
* @return The driver's feature level.
*/
// Resource updates
/** @fn Driver::setVertexBufferObject(backend::VertexBufferHandle vbh, uint32_t index, backend::BufferObjectHandle bufferObject)
* @brief Associates a buffer object with a specific buffer slot in a vertex buffer.
* @param vbh The handle of the vertex buffer.
* @param index The index of the buffer slot to set.
* @param bufferObject The handle of the buffer object to associate.
*/
/** @fn Driver::updateIndexBuffer(backend::IndexBufferHandle ibh, backend::BufferDescriptor&& data, uint32_t byteOffset)
* @brief Updates the data of an index buffer.
* @param ibh The handle of the index buffer.
* @param data The new data to upload.
* @param byteOffset The offset in bytes into the buffer to start writing to.
*/
/** @fn Driver::updateBufferObject(backend::BufferObjectHandle boh, backend::BufferDescriptor&& data, uint32_t byteOffset)
* @brief Updates the data of a buffer object.
* @param boh The handle of the buffer object.
* @param data The new data to upload.
* @param byteOffset The offset in bytes into the buffer to start writing to.
*/
/** @fn Driver::registerBufferObjectStreams(backend::BufferObjectHandle boh, backend::BufferObjectStreamDescriptor&& streams)
* @brief Registers external streams with a buffer object for efficient data transfer.
* @param boh The handle of the buffer object.
* @param streams The stream descriptors.
*/
/** @fn Driver::updateBufferObjectUnsynchronized(backend::BufferObjectHandle boh, backend::BufferDescriptor&& data, uint32_t byteOffset)
* @brief Updates a buffer object without synchronization, for use when manual synchronization is handled.
* @param boh The handle of the buffer object.
* @param data The new data to upload.
* @param byteOffset The offset in bytes into the buffer to start writing to.
*/
/** @fn Driver::resetBufferObject(backend::BufferObjectHandle boh)
* @brief Resets a buffer object, potentially discarding its contents.
* @param boh The handle of the buffer object.
*/
/** @fn Driver::update3DImage(backend::TextureHandle th, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth, backend::PixelBufferDescriptor&& data)
* @brief Updates a sub-region of a 3D texture.
* @param th The handle of the texture.
* @param level The mipmap level to update.
* @param xoffset The x offset of the sub-region.
* @param yoffset The y offset of the sub-region.
* @param zoffset The z offset of the sub-region.
* @param width The width of the sub-region.
* @param height The height of the sub-region.
* @param depth The depth of the sub-region.
* @param data The pixel data to upload.
*/
/** @fn Driver::generateMipmaps(backend::TextureHandle th)
* @brief Generates mipmaps for a texture automatically.
* @param th The handle of the texture.
*/
/** @fn Driver::setExternalStream(backend::TextureHandle th, backend::StreamHandle sh)
* @brief Associates an external stream with a texture for video or camera input.
* @param th The handle of the texture.
* @param sh The handle of the stream.
*/
// Render passes
/** @fn Driver::beginRenderPass(backend::RenderTargetHandle rth, const backend::RenderPassParams& params)
* @brief Begins a new render pass, targeting a specific render target.
* @param rth The handle of the render target.
* @param params The parameters for the render pass (e.g., clear values, load/store actions).
*/
/** @fn Driver::endRenderPass()
* @brief Ends the current render pass.
*/
/** @fn Driver::nextSubpass()
* @brief Moves to the next subpass within the current render pass.
*/
// Timer queries
/** @fn Driver::beginTimerQuery(backend::TimerQueryHandle query)
* @brief Begins a timer query to measure GPU time.
* @param query The handle of the timer query.
*/
/** @fn Driver::endTimerQuery(backend::TimerQueryHandle query)
* @brief Ends a timer query.
* @param query The handle of the timer query.
*/
/** @fn Driver::compilePrograms(backend::CompilerPriorityQueue priority, backend::CallbackHandler* handler, backend::CallbackHandler::Callback callback, void* user)
* @brief Compiles a queue of pending shader programs.
* @param priority The priority queue to use for compilation.
* @param handler The callback handler.
* @param callback The callback function to be invoked upon completion.
* @param user User data for the callback.
*/
// Swap chain
/** @fn Driver::makeCurrent(backend::SwapChainHandle schDraw, backend::SwapChainHandle schRead)
* @brief Makes a swap chain current for drawing and reading.
* @param schDraw The draw swap chain.
* @param schRead The read swap chain.
*/
/** @fn Driver::commit(backend::SwapChainHandle sch)
* @brief Commits the back buffer of a swap chain, making it visible.
* @param sch The handle of the swap chain.
*/
// Rendering state
/** @fn Driver::setPushConstant(backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant value)
* @brief Sets a push constant value for a specific shader stage.
* @param stage The shader stage to set the constant for.
* @param index The index of the push constant.
* @param value The value to set.
*/
/** @fn Driver::insertEventMarker(const char* string)
* @brief Inserts a debug event marker into the command stream.
* @param string The marker string.
*/
/** @fn Driver::pushGroupMarker(const char* string)
* @brief Pushes a debug group marker onto the command stream stack.
* @param string The marker string.
*/
/** @fn Driver::popGroupMarker()
* @brief Pops a debug group marker from the command stream stack.
*/
/** @fn Driver::startCapture()
* @brief Starts a graphics capture session.
*/
/** @fn Driver::stopCapture()
* @brief Stops a graphics capture session.
*/
// Read-back
/** @fn Driver::readPixels(backend::RenderTargetHandle src, uint32_t x, uint32_t y, uint32_t width, uint32_t height, backend::PixelBufferDescriptor&& data)
* @brief Reads a block of pixels from a render target into a client-side buffer.
* @param src The source render target.
* @param x The x coordinate of the region to read.
* @param y The y coordinate of the region to read.
* @param width The width of the region to read.
* @param height The height of the region to read.
* @param data The buffer to store the pixel data.
*/
/** @fn Driver::readBufferSubData(backend::BufferObjectHandle src, uint32_t offset, uint32_t size, backend::BufferDescriptor&& data)
* @brief Reads a sub-region of a buffer object into a client-side buffer.
* @param src The source buffer object.
* @param offset The offset to start reading from.
* @param size The number of bytes to read.
* @param data The buffer to store the data.
*/
// Rendering
/** @fn Driver::blitDEPRECATED(backend::TargetBufferFlags buffers, backend::RenderTargetHandle dst, backend::Viewport dstRect, backend::RenderTargetHandle src, backend::Viewport srcRect, backend::SamplerMagFilter filter)
* @brief Blits (copies) a region from one render target to another (deprecated).
* @param buffers The buffers to blit (e.g., color, depth).
* @param dst The destination render target.
* @param dstRect The destination rectangle.
* @param src The source render target.
* @param srcRect The source rectangle.
* @param filter The filter to use for scaling.
*/
/** @fn Driver::resolve(backend::TextureHandle dst, uint8_t dstLevel, uint8_t dstLayer, backend::TextureHandle src, uint8_t srcLevel, uint8_t srcLayer)
* @brief Resolves a multisampled texture into a non-multisampled texture.
* @param dst The destination texture.
* @param dstLevel The destination mipmap level.
* @param dstLayer The destination layer.
* @param src The source multisampled texture.
* @param srcLevel The source mipmap level.
* @param srcLayer The source layer.
*/
/** @fn Driver::blit(backend::TextureHandle dst, uint8_t dstLevel, uint8_t dstLayer, math::uint2 dstOrigin, backend::TextureHandle src, uint8_t srcLevel, uint8_t srcLayer, math::uint2 srcOrigin, math::uint2 size)
* @brief Blits (copies) a region from one texture to another.
* @param dst The destination texture.
* @param dstLevel The destination mipmap level.
* @param dstLayer The destination layer.
* @param dstOrigin The origin of the destination region.
* @param src The source texture.
* @param srcLevel The source mipmap level.
* @param srcLayer The source layer.
* @param srcOrigin The origin of the source region.
* @param size The size of the region to blit.
*/
/** @fn Driver::bindPipeline(const backend::PipelineState& state)
* @brief Binds a pipeline state object, including shaders and render states.
* @param state The pipeline state to bind.
*/
/** @fn Driver::bindRenderPrimitive(backend::RenderPrimitiveHandle rph)
* @brief Binds a render primitive for subsequent draw calls.
* @param rph The handle of the render primitive.
*/
/** @fn Driver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount)
* @brief Draws a render primitive using the currently bound pipeline and primitive.
* @param indexOffset The offset into the index buffer.
* @param indexCount The number of indices to draw.
* @param instanceCount The number of instances to draw.
*/
/** @fn Driver::draw(backend::PipelineState state, backend::RenderPrimitiveHandle rph, uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount)
* @brief A combined call to bind a pipeline, bind a primitive, and draw.
* @param state The pipeline state to bind.
* @param rph The render primitive to bind.
* @param indexOffset The offset into the index buffer.
* @param indexCount The number of indices to draw.
* @param instanceCount The number of instances to draw.
*/
/** @fn Driver::dispatchCompute(backend::ProgramHandle program, math::uint3 workGroupCount)
* @brief Dispatches a compute shader.
* @param program The handle of the compute program.
* @param workGroupCount The number of work groups to dispatch in each dimension.
*/
/** @fn Driver::scissor(Viewport scissor)
* @brief Sets the scissor rectangle for clipping.
* @param scissor The scissor rectangle.
*/

View File

@@ -156,27 +156,27 @@ void VulkanStagePool::destroyStage(VulkanStage const*&& stage) {
delete stage;
}
fvkmemory::resource_ptr<VulkanStageImage::Resource> VulkanStagePool::acquireImage(
PixelDataFormat format, PixelDataType type, uint32_t width, uint32_t height) {
// Helper lambda so we can return stage images wrapped as resources that can
// be held by command buffers until no longer needed.
auto wrapAsResource = [this](VulkanStageImage* image) {
auto recycleFn = [this](VulkanStageImage* image) {
this->mFreeImages.insert(image);
};
return fvkmemory::resource_ptr<VulkanStageImage::Resource>::construct(
this->mResManager, image, recycleFn);
};
VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, PixelDataType type,
uint32_t width, uint32_t height) {
const VkFormat vkformat = fvkutils::getVkFormat(format, type);
for (auto stageImage : mFreeImages) {
if (stageImage->format() == vkformat && stageImage->width() == width && stageImage->height() == height) {
mFreeImages.erase(stageImage);
stageImage->mLastAccessed = mCurrentFrame;
return wrapAsResource(stageImage);
for (auto image : mFreeImages) {
if (image->format == vkformat && image->width == width && image->height == height) {
mFreeImages.erase(image);
image->lastAccessed = mCurrentFrame;
mUsedImages.push_back(image);
return image;
}
}
VulkanStageImage* image = new VulkanStageImage({
.format = vkformat,
.width = width,
.height = height,
.lastAccessed = mCurrentFrame,
});
mUsedImages.push_back(image);
const VkImageCreateInfo imageInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
@@ -194,19 +194,14 @@ fvkmemory::resource_ptr<VulkanStageImage::Resource> VulkanStagePool::acquireImag
.usage = VMA_MEMORY_USAGE_CPU_TO_GPU
};
VkImage image;
VmaAllocation memory;
const UTILS_UNUSED VkResult result = vmaCreateImage(mAllocator, &imageInfo, &allocInfo,
&image, &memory, nullptr);
&image->image, &image->memory, nullptr);
assert_invariant(result == VK_SUCCESS);
VkImageAspectFlags const aspectFlags = fvkutils::getImageAspect(vkformat);
VkCommandBuffer const cmdbuffer = mCommands->get().buffer();
VulkanStageImage* stageImage = new VulkanStageImage(
vkformat, width, height, memory, image, mCurrentFrame);
// We use VK_IMAGE_LAYOUT_GENERAL here because the spec says:
// "Host access to image memory is only well-defined for linear images and for image
// subresources of those images which are currently in either the
@@ -214,13 +209,12 @@ fvkmemory::resource_ptr<VulkanStageImage::Resource> VulkanStagePool::acquireImag
// vkGetImageSubresourceLayout for a linear image returns a subresource layout mapping that is
// valid for either of those image layouts."
fvkutils::transitionLayout(cmdbuffer, {
.image = stageImage->image(),
.image = image->image,
.oldLayout = VulkanLayout::UNDEFINED,
.newLayout = VulkanLayout::STAGING, // (= VK_IMAGE_LAYOUT_GENERAL)
.subresources = { aspectFlags, 0, 1, 0, 1 },
});
return wrapAsResource(stageImage);
return image;
}
void VulkanStagePool::gc() noexcept {
@@ -268,14 +262,25 @@ void VulkanStagePool::gc() noexcept {
decltype(mFreeImages) freeImages;
freeImages.swap(mFreeImages);
for (auto image : freeImages) {
if (image->mLastAccessed < evictionTime) {
vmaDestroyImage(mAllocator, image->image(), image->memory());
if (image->lastAccessed < evictionTime) {
vmaDestroyImage(mAllocator, image->image, image->memory);
delete image;
} else {
mFreeImages.insert(image);
}
}
// Reclaim images that are no longer being used by any command buffer.
decltype(mUsedImages) usedImages;
usedImages.swap(mUsedImages);
for (auto image : usedImages) {
if (image->lastAccessed < evictionTime) {
image->lastAccessed = mCurrentFrame;
mFreeImages.insert(image);
} else {
mUsedImages.push_back(image);
}
}
FVK_SYSTRACE_END();
}
@@ -285,8 +290,14 @@ void VulkanStagePool::terminate() noexcept {
}
mStages.clear();
for (auto image : mUsedImages) {
vmaDestroyImage(mAllocator, image->image, image->memory);
delete image;
}
mUsedImages.clear();
for (auto image : mFreeImages) {
vmaDestroyImage(mAllocator, image->image(), image->memory());
vmaDestroyImage(mAllocator, image->image, image->memory);
delete image;
}
mFreeImages.clear();

View File

@@ -126,70 +126,13 @@ private:
std::unordered_map<uint32_t, Segment*> mSegments;
};
class VulkanStageImage {
public:
class Resource : public fvkmemory::Resource {
public:
using RecycleFn = std::function<void(VulkanStageImage*)>;
Resource(VulkanStageImage* image, RecycleFn&& onRecycleFn)
: mImage(image),
mOnRecycleFn(onRecycleFn)
{}
~Resource() {
if (mOnRecycleFn) {
mOnRecycleFn(mImage);
}
}
inline VkFormat format() const { return mImage->format(); }
inline uint32_t width() const { return mImage->width(); }
inline uint32_t height() const { return mImage->height(); }
inline VmaAllocation memory() const { return mImage->memory(); }
inline VkImage image() const { return mImage->image(); }
private:
Resource() = delete;
Resource(const Resource& other) = delete;
Resource(Resource&& other) = delete;
Resource& operator=(const Resource& other) = delete;
Resource& operator=(Resource&& other) = delete;
VulkanStageImage* const mImage;
RecycleFn mOnRecycleFn;
};
VulkanStageImage(VkFormat format, uint32_t width, uint32_t height, VmaAllocation memory,
VkImage image, uint64_t lastAccessed)
: mFormat(format),
mWidth(width),
mHeight(height),
mMemory(memory),
mImage(image),
mLastAccessed(lastAccessed) {}
VulkanStageImage(const VulkanStageImage& other) = delete;
VulkanStageImage(VulkanStageImage&& other) = delete;
VulkanStageImage& operator=(const VulkanStageImage& other) = delete;
VulkanStageImage& operator=(VulkanStageImage&& other) = delete;
inline VkFormat format() const { return mFormat; }
inline uint32_t width() const { return mWidth; }
inline uint32_t height() const { return mHeight; }
inline VmaAllocation memory() const { return mMemory; }
inline VkImage image() const { return mImage; }
private:
const VkFormat mFormat;
const uint32_t mWidth;
const uint32_t mHeight;
const VmaAllocation mMemory;
const VkImage mImage;
uint64_t mLastAccessed;
// Denote as a friend so that it can update mLastAccessed.
friend class VulkanStagePool;
struct VulkanStageImage {
VkFormat format;
uint32_t width;
uint32_t height;
mutable uint64_t lastAccessed;
VmaAllocation memory;
VkImage image;
};
// Manages a pool of stages, periodically releasing stages that have been unused for a while.
@@ -210,7 +153,7 @@ public:
uint32_t alignment = 0);
// Images have VK_IMAGE_LAYOUT_GENERAL and must not be transitioned to any other layout
fvkmemory::resource_ptr<VulkanStageImage::Resource> acquireImage(PixelDataFormat format, PixelDataType type,
VulkanStageImage const* acquireImage(PixelDataFormat format, PixelDataType type,
uint32_t width, uint32_t height);
// Evicts old unused stages and bumps the current frame number.
@@ -250,7 +193,8 @@ private:
// Use an ordered multimap for quick (capacity => stage) lookups using lower_bound().
std::multimap<uint32_t, VulkanStage*> mStages;
std::unordered_set<VulkanStageImage*> mFreeImages;
std::unordered_set<VulkanStageImage const*> mFreeImages;
std::vector<VulkanStageImage const*> mUsedImages;
// Store the current "time" (really just a frame count) and LRU eviction parameters.
uint64_t mCurrentFrame = 0;

View File

@@ -602,16 +602,15 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& data, uint3
size_t const writeSize = bpp > 0 ? width * height * depth * bpp : data.size;
void* mapped = nullptr;
fvkmemory::resource_ptr<VulkanStageImage::Resource> stage
VulkanStageImage const* stage
= mState->mStagePool.acquireImage(data.format, data.type, width, height);
vmaMapMemory(mState->mAllocator, stage->memory(), &mapped);
vmaMapMemory(mState->mAllocator, stage->memory, &mapped);
adjustedMemcpy(mapped, data, width, height, depth);
vmaUnmapMemory(mState->mAllocator, stage->memory());
vmaFlushAllocation(mState->mAllocator, stage->memory(), 0, writeSize);
vmaUnmapMemory(mState->mAllocator, stage->memory);
vmaFlushAllocation(mState->mAllocator, stage->memory, 0, writeSize);
VulkanCommandBuffer& commands = mState->mCommands->get();
VkCommandBuffer const cmdbuf = commands.buffer();
commands.acquire(stage);
commands.acquire(fvkmemory::resource_ptr<VulkanTexture>::cast(this));
// TODO: support blit-based format conversion for 3D images and cubemaps.
@@ -634,7 +633,7 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& data, uint3
VulkanLayout const oldLayout = getLayout(layer, miplevel);
transitionLayout(&commands, range, newLayout);
vkCmdBlitImage(cmdbuf, stage->image(), fvkutils::getVkLayout(VulkanLayout::TRANSFER_SRC),
vkCmdBlitImage(cmdbuf, stage->image, fvkutils::getVkLayout(VulkanLayout::TRANSFER_SRC),
mState->mTextureImage, fvkutils::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST);
transitionLayout(&commands, range, oldLayout);

View File

@@ -27,7 +27,6 @@ template ResourceType getTypeEnum<VulkanProgram>() noexcept;
template ResourceType getTypeEnum<VulkanRenderTarget>() noexcept;
template ResourceType getTypeEnum<VulkanSwapChain>() noexcept;
template ResourceType getTypeEnum<VulkanStage::Segment>() noexcept;
template ResourceType getTypeEnum<VulkanStageImage::Resource>() noexcept;
template ResourceType getTypeEnum<VulkanRenderPrimitive>() noexcept;
template ResourceType getTypeEnum<VulkanTexture>() noexcept;
template ResourceType getTypeEnum<VulkanTextureState>() noexcept;
@@ -59,9 +58,6 @@ ResourceType getTypeEnum() noexcept {
if constexpr (std::is_same_v<D, VulkanStage::Segment>) {
return ResourceType::STAGE_SEGMENT;
}
if constexpr (std::is_same_v<D, VulkanStageImage::Resource>) {
return ResourceType::STAGE_IMAGE;
}
if constexpr (std::is_same_v<D, VulkanRenderPrimitive>) {
return ResourceType::RENDER_PRIMITIVE;
}
@@ -109,8 +105,6 @@ std::string getTypeStr(ResourceType type) {
return "SwapChain";
case ResourceType::STAGE_SEGMENT:
return "Stage::Segment";
case ResourceType::STAGE_IMAGE:
return "Stage::Image";
case ResourceType::RENDER_PRIMITIVE:
return "RenderPrimitive";
case ResourceType::TEXTURE:

View File

@@ -51,8 +51,7 @@ enum class ResourceType : uint8_t {
FENCE = 13,
VULKAN_BUFFER = 14,
STAGE_SEGMENT = 15,
STAGE_IMAGE = 16,
UNDEFINED_TYPE = 17, // Must be the last enum because we use it for iterating over the enums.
UNDEFINED_TYPE = 16, // Must be the last enum because we use it for iterating over the enums.
};
template<typename D>

View File

@@ -81,9 +81,6 @@ void ResourceManager::destroyWithType(ResourceType type, HandleId id) {
case ResourceType::STAGE_SEGMENT:
destruct<VulkanStage::Segment>(Handle<VulkanStage::Segment>(id));
break;
case ResourceType::STAGE_IMAGE:
destruct<VulkanStageImage::Resource>(Handle<VulkanStageImage::Resource>(id));
break;
case ResourceType::RENDER_PRIMITIVE:
destruct<VulkanRenderPrimitive>(Handle<VulkanRenderPrimitive>(id));
break;

View File

@@ -591,7 +591,7 @@ bool WebGPUDriver::isParallelShaderCompileSupported() {
}
bool WebGPUDriver::isDepthStencilResolveSupported() {
return false;
return true;
}
bool WebGPUDriver::isDepthStencilBlitSupported(const TextureFormat format) {
@@ -1215,6 +1215,12 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
}
}
// TODO: We expected this to be a sane check, however it complains when running shadowtest.
//if (program->fragmentShaderModule != nullptr) {
// FILAMENT_CHECK_POSTCONDITION(!pipelineColorFormats.empty())
// << "Render pipeline with fragment shader must have at least one color target "
// "format.";
//}
wgpu::RenderPipeline pipeline = createWebGPURenderPipeline(mDevice, *program, *vertexBufferInfo,
layout, pipelineState.rasterState, pipelineState.stencilState,
pipelineState.polygonOffset, pipelineState.primitiveType, pipelineColorFormats,

View File

@@ -281,32 +281,27 @@ wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
}
};
if (program.fragmentShaderModule != nullptr) {
// According to the WebGPU spec, a pipeline cannot have a fragment stage with zero color
// targets. This situation can arise in Filament during depth-only passes (like shadow map
// generation) if the material variant still includes a fragment shader.
//
// To handle this, we check if any color targets are configured for this pipeline. If not, we
// create a pipeline *without* a fragment stage. This makes the pipeline valid for a
// depth-only pass, allowing depth writes to proceed correctly.
if (!colorFormats.empty()) {
fragmentState.module = program.fragmentShaderModule;
fragmentState.entryPoint = "main";
// see the comment about constants for the vertex state, as the same reasoning applies
// here
fragmentState.constantCount = 0,
fragmentState.constants = nullptr,
fragmentState.targetCount = colorFormats.size();
fragmentState.targets = colorTargets.data();
assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) {
auto& colorTarget = colorTargets[targetIndex];
colorTarget.format = colorFormats[targetIndex];
colorTarget.blend = rasterState.hasBlending() ? &blendState : nullptr;
colorTarget.writeMask =
rasterState.colorWrite ? wgpu::ColorWriteMask::All : wgpu::ColorWriteMask::None;
}
pipelineDescriptor.fragment = &fragmentState;
fragmentState.module = program.fragmentShaderModule;
fragmentState.entryPoint = "main";
// see the comment about constants for the vertex state, as the same reasoning applies
// here
fragmentState.constantCount = 0,
fragmentState.constants = nullptr,
fragmentState.targetCount = colorFormats.size();
fragmentState.targets = colorTargets.data();
assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
// We expect a fragment shader implies at least one color target if it outputs color.
// This should be guaranteed by the caller ensuring colorFormats is not empty.
// However, this fails on shadowtest.cpp, TODO investigate why
// assert_invariant(fragmentState.targetCount > 0);
for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) {
auto& colorTarget = colorTargets[targetIndex];
colorTarget.format = colorFormats[targetIndex];
colorTarget.blend = rasterState.hasBlending() ? &blendState : nullptr;
colorTarget.writeMask =
rasterState.colorWrite ? wgpu::ColorWriteMask::All : wgpu::ColorWriteMask::None;
}
pipelineDescriptor.fragment = &fragmentState;
}
const wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor);
FILAMENT_CHECK_POSTCONDITION(pipeline)

View File

@@ -118,6 +118,7 @@ INPUT = ../libs/filabridge/include \
../libs/gltfio/include \
../libs/utils/include \
backend/include \
backend/include/private/backend/DriverAPI.dox \
include
INPUT_ENCODING = UTF-8

View File

@@ -82,6 +82,8 @@ RenderPassBuilder& RenderPassBuilder::customCommand(
RenderPass RenderPassBuilder::build(FEngine const& engine, DriverApi& driver) const {
assert_invariant(mRenderableSoa);
assert_invariant(mScissorViewport.width <= std::numeric_limits<int32_t>::max());
assert_invariant(mScissorViewport.height <= std::numeric_limits<int32_t>::max());
return RenderPass{ engine, driver, *this };
}
@@ -105,7 +107,8 @@ void RenderPass::DescriptorSetHandleDeleter::operator()(
RenderPass::RenderPass(FEngine const& engine, DriverApi& driver,
RenderPassBuilder const& builder) noexcept
: mRenderableSoa(*builder.mRenderableSoa),
mColorPassDescriptorSet(builder.mColorPassDescriptorSet) {
mColorPassDescriptorSet(builder.mColorPassDescriptorSet),
mScissorViewport(builder.mScissorViewport) {
// compute the number of commands we need
updateSummedPrimitiveCounts(

View File

@@ -306,12 +306,6 @@ public:
// allocated commands ARE NOT freed, they're owned by the Arena
~RenderPass() noexcept;
// Specifies the viewport for the scissor rectangle, that is, the final scissor rect is
// offset by the viewport's left-top and clipped to the viewport's width/height.
void setScissorViewport(backend::Viewport const viewport) noexcept {
mScissorViewport = viewport;
}
Command const* begin() const noexcept { return mCommandBegin; }
Command const* end() const noexcept { return mCommandEnd; }
bool empty() const noexcept { return begin() == end(); }
@@ -467,7 +461,7 @@ private:
FScene::RenderableSoa const& mRenderableSoa;
ColorPassDescriptorSet const* const mColorPassDescriptorSet;
backend::Viewport mScissorViewport{ 0, 0, INT32_MAX, INT32_MAX };
backend::Viewport const mScissorViewport{ 0, 0, INT32_MAX, INT32_MAX };
Command const* /* const */ mCommandBegin = nullptr; // Pointer to the first command
Command const* /* const */ mCommandEnd = nullptr; // Pointer to one past the last command
mutable BufferObjectSharedHandle mInstancedUboHandle; // ubo for instanced primitives
@@ -482,6 +476,7 @@ class RenderPassBuilder {
RenderPass::Arena& mArena;
RenderPass::CommandTypeFlags mCommandTypeFlags{};
backend::Viewport mScissorViewport{ 0, 0, INT32_MAX, INT32_MAX };
FScene::RenderableSoa const* mRenderableSoa = nullptr;
utils::Range<uint32_t> mVisibleRenderables{};
math::float3 mCameraPosition{};
@@ -512,6 +507,13 @@ public:
return *this;
}
// Specifies the viewport for the scissor rectangle, that is, the final scissor rect is
// offset by the viewport's left-top and clipped to the viewport's width/height.
RenderPassBuilder& scissorViewport(backend::Viewport const viewport) noexcept {
mScissorViewport = viewport;
return *this;
}
// specifies the geometry to generate commands for
RenderPassBuilder& geometry(
FScene::RenderableSoa const& soa, utils::Range<uint32_t> const vr) noexcept {

View File

@@ -40,6 +40,7 @@
#include <utils/Panic.h>
#include <algorithm>
#include <optional>
#include <utility>
#include <stddef.h>
@@ -269,94 +270,87 @@ RendererUtils::ColorPassOutput RendererUtils::colorPass(
};
}
RenderPass::Command const* RendererUtils::getFirstRefractionCommand(
RenderPass const& pass) noexcept {
// find the first refractive object in channel 2
RenderPass::Command const* const refraction = std::partition_point(pass.begin(), pass.end(),
[](auto const& command) {
constexpr uint64_t mask = RenderPass::CHANNEL_MASK | RenderPass::PASS_MASK;
constexpr uint64_t channel = uint64_t(RenderableManager::Builder::DEFAULT_CHANNEL) << RenderPass::CHANNEL_SHIFT;
constexpr uint64_t value = channel | uint64_t(RenderPass::Pass::REFRACT);
return (command.key & mask) < value;
});
const bool hasScreenSpaceRefraction =
(refraction->key & RenderPass::PASS_MASK) == uint64_t(RenderPass::Pass::REFRACT);
return hasScreenSpaceRefraction ? refraction : nullptr;
}
RendererUtils::ColorPassOutput RendererUtils::refractionPass(
std::optional<RendererUtils::ColorPassOutput> RendererUtils::refractionPass(
FrameGraph& fg, FEngine& engine, FView const& view,
ColorPassInput colorPassInput,
ColorPassConfig config,
PostProcessManager::ScreenSpaceRefConfig const& ssrConfig,
PostProcessManager::ColorGradingConfig const colorGradingConfig,
RenderPass const& pass, RenderPass::Command const* const firstRefractionCommand) noexcept {
RenderPass const& pass) noexcept {
assert_invariant(firstRefractionCommand);
RenderPass::Command const* const refraction = firstRefractionCommand;
// find the first refractive object in channel 2
RenderPass::Command const* const refraction = std::partition_point(pass.begin(), pass.end(),
[](auto const& command) {
constexpr uint64_t mask = RenderPass::CHANNEL_MASK | RenderPass::PASS_MASK;
constexpr uint64_t channel = uint64_t(RenderableManager::Builder::DEFAULT_CHANNEL) << RenderPass::CHANNEL_SHIFT;
constexpr uint64_t value = channel | uint64_t(RenderPass::Pass::REFRACT);
return (command.key & mask) < value;
});
const bool hasScreenSpaceRefraction =
(refraction->key & RenderPass::PASS_MASK) == uint64_t(RenderPass::Pass::REFRACT);
// if there wasn't any refractive object, just skip everything below.
assert_invariant(!colorPassInput.linearColor);
assert_invariant(!colorPassInput.depth);
config.hasScreenSpaceReflectionsOrRefractions = true;
if (UTILS_UNLIKELY(hasScreenSpaceRefraction)) {
assert_invariant(!colorPassInput.linearColor);
assert_invariant(!colorPassInput.depth);
config.hasScreenSpaceReflectionsOrRefractions = true;
PostProcessManager& ppm = engine.getPostProcessManager();
auto const opaquePassOutput = colorPass(fg,
"Color Pass (opaque)", engine, view, colorPassInput, {
// When rendering the opaques, we need to conserve the sample buffer,
// so create a config that specifies the sample count.
.width = config.physicalViewport.width,
.height = config.physicalViewport.height,
.samples = config.msaa,
.format = config.hdrFormat
},
config, { .asSubpass = false, .customResolve = false },
pass.getExecutor(pass.begin(), refraction));
PostProcessManager& ppm = engine.getPostProcessManager();
auto const opaquePassOutput = colorPass(fg,
"Color Pass (opaque)", engine, view, colorPassInput, {
// When rendering the opaques, we need to conserve the sample buffer,
// so create a config that specifies the sample count.
.width = config.physicalViewport.width,
.height = config.physicalViewport.height,
.samples = config.msaa,
.format = config.hdrFormat
},
config, { .asSubpass = false, .customResolve = false },
pass.getExecutor(pass.begin(), refraction));
// Generate the mipmap chain
// Note: we can run some post-processing effects while the "color pass" descriptor set
// in bound because only the descriptor 0 (frame uniforms) matters, and it's
// present in both.
PostProcessManager::generateMipmapSSR(ppm, fg,
opaquePassOutput.linearColor,
ssrConfig.refraction,
true, ssrConfig);
// Generate the mipmap chain
// Note: we can run some post-processing effects while the "color pass" descriptor set
// in bound because only the descriptor 0 (frame uniforms) matters, and it's
// present in both.
PostProcessManager::generateMipmapSSR(ppm, fg,
opaquePassOutput.linearColor,
ssrConfig.refraction,
true, ssrConfig);
// Now we're doing the refraction pass proper.
// This uses the same framebuffer (color and depth) used by the opaque pass.
// For this reason, the `colorBufferDesc` parameter of colorPass() below is only used for
// the width and height.
colorPassInput.linearColor = opaquePassOutput.linearColor;
colorPassInput.depth = opaquePassOutput.depth;
// Now we're doing the refraction pass proper.
// This uses the same framebuffer (color and depth) used by the opaque pass.
// For this reason, the `colorBufferDesc` parameter of colorPass() below is only used for
// the width and height.
colorPassInput.linearColor = opaquePassOutput.linearColor;
colorPassInput.depth = opaquePassOutput.depth;
// Since we're reusing the existing target we don't want to clear any of its buffer.
// Important: if this target ended up being an imported target, then the clearFlags
// specified here wouldn't apply (the clearFlags of the imported target take precedence),
// and we'd end up clearing the opaque pass. This scenario never happens because it is
// prevented in Renderer.cpp's final blit.
config.clearFlags = TargetBufferFlags::NONE;
auto transparentPassOutput = colorPass(fg, "Color Pass (transparent)",
engine, view, colorPassInput, {
.width = config.physicalViewport.width,
.height = config.physicalViewport.height },
config, colorGradingConfig,
pass.getExecutor(refraction, pass.end()));
// Since we're reusing the existing target we don't want to clear any of its buffer.
// Important: if this target ended up being an imported target, then the clearFlags
// specified here wouldn't apply (the clearFlags of the imported target take precedence),
// and we'd end up clearing the opaque pass. This scenario never happens because it is
// prevented in Renderer.cpp's final blit.
config.clearFlags = TargetBufferFlags::NONE;
auto transparentPassOutput = colorPass(fg, "Color Pass (transparent)",
engine, view, colorPassInput, {
.width = config.physicalViewport.width,
.height = config.physicalViewport.height },
config, colorGradingConfig,
pass.getExecutor(refraction, pass.end()));
if (config.msaa > 1 && !colorGradingConfig.asSubpass) {
// We need to do a resolve here because later passes (such as color grading or DoF) will
// need to sample from 'output'. However, because we have MSAA, we know we're not
// sampleable. And this is because in the SSR case, we had to use a renderbuffer to
// conserve the multi-sample buffer.
transparentPassOutput.linearColor = ppm.resolve(fg, "Resolved Color Buffer",
transparentPassOutput.linearColor, { .levels = 1 });
if (config.msaa > 1 && !colorGradingConfig.asSubpass) {
// We need to do a resolve here because later passes (such as color grading or DoF) will
// need to sample from 'output'. However, because we have MSAA, we know we're not
// sampleable. And this is because in the SSR case, we had to use a renderbuffer to
// conserve the multi-sample buffer.
transparentPassOutput.linearColor = ppm.resolve(fg, "Resolved Color Buffer",
transparentPassOutput.linearColor, { .levels = 1 });
}
return transparentPassOutput;
}
return transparentPassOutput;
return std::nullopt;
}
UTILS_NOINLINE

View File

@@ -26,18 +26,17 @@
#include <filament/Viewport.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <backend/PixelBufferDescriptor.h>
#include <math/vec2.h>
#include <math/vec4.h>
#include <stdint.h>
namespace filament {
#include <optional>
#include <utility>
namespace backend {
class PixelBufferDescriptor;
}
namespace filament {
class FRenderTarget;
class FrameGraph;
@@ -100,20 +99,18 @@ public:
PostProcessManager::ColorGradingConfig colorGradingConfig,
RenderPass::Executor passExecutor) noexcept;
static ColorPassOutput refractionPass(
static std::optional<ColorPassOutput> refractionPass(
FrameGraph& fg, FEngine& engine, FView const& view,
ColorPassInput colorPassInput,
ColorPassConfig config,
PostProcessManager::ScreenSpaceRefConfig const& ssrConfig,
PostProcessManager::ColorGradingConfig colorGradingConfig,
RenderPass const& pass, RenderPass::Command const* firstRefractionCommand) noexcept;
RenderPass const& pass) noexcept;
static void readPixels(backend::DriverApi& driver,
backend::Handle<backend::HwRenderTarget> renderTargetHandle,
uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
backend::PixelBufferDescriptor&& buffer);
static RenderPass::Command const* getFirstRefractionCommand(RenderPass const& pass) noexcept;
};
} // namespace filament

View File

@@ -1125,6 +1125,16 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
// --------------------------------------------------------------------------------------------
// Color passes
// this makes the viewport relative to xvp
// FIXME: we should use 'vp' when rendering directly into the swapchain, but that's hard to
// know at this point. This will usually be the case when post-process is disabled.
// FIXME: we probably should take the dynamic scaling into account too
// if MSAA is enabled, we end-up rendering in an intermediate buffer. This is the only case where
// "!hasPostProcess" doesn't guarantee rendering into the swapchain.
const bool useIntermediateBuffer = hasPostProcess || msaaOptions.enabled ||
(isRenderingMultiview && engine.debug.stereo.combine_multiview_images);
passBuilder.scissorViewport(useIntermediateBuffer ? xvp : vp);
// This one doesn't need to be a FrameGraph pass because it always happens by construction
// (i.e. it won't be culled, unless everything is culled), so no need to complexify things.
passBuilder.variant(variant);
@@ -1164,35 +1174,8 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
passBuilder.renderFlags(renderFlags);
}
// create the pass, which generates all its commands (this is a heavy operation)
RenderPass const pass{ passBuilder.build(engine, driver) };
// now that we have the commands we can figure out if we have refraction commands
auto* const firstRefractionCommand = [&view](RenderPass const& pass) {
RenderPass::Command const* p = nullptr;
if (UTILS_UNLIKELY(view.isScreenSpaceRefractionEnabled() && !pass.empty())) {
p = RendererUtils::getFirstRefractionCommand(pass);
}
return p;
}(pass);
hasScreenSpaceRefraction = firstRefractionCommand != nullptr;
// this makes the viewport relative to xvp
// FIXME: we should use 'vp' when rendering directly into the swapchain, but that's hard to
// know at this point. This will usually be the case when post-process is disabled.
// FIXME: we probably should take the dynamic scaling into account too
// if MSAA is enabled, we end-up rendering in an intermediate buffer. This is the only case where
// "!hasPostProcess" doesn't guarantee rendering into the swapchain.
const bool useIntermediateBuffer = hasPostProcess || msaaOptions.enabled ||
ssReflectionsOptions.enabled || hasScreenSpaceRefraction ||
(isRenderingMultiview && engine.debug.stereo.
combine_multiview_images);
// this is slightly ugly, but conceptually `pass` is const; it's just that we can't set
// the scissor viewport during construction
const_cast<RenderPass&>(pass).setScissorViewport(useIntermediateBuffer ? xvp : vp);
FrameGraphTexture::Descriptor colorBufferDesc = {
.width = config.physicalViewport.width,
.height = config.physicalViewport.height,
@@ -1237,17 +1220,21 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
},
colorBufferDesc, config, colorGradingConfigForColor, pass.getExecutor());
if (UTILS_UNLIKELY(hasScreenSpaceRefraction)) {
if (view.isScreenSpaceRefractionEnabled() && !pass.empty()) {
// This cancels the colorPass() call above if refraction is active.
// The color pass + refraction + color-grading as subpass if needed
colorPassOutput = RendererUtils::refractionPass(fg, mEngine, view, {
auto const output = RendererUtils::refractionPass(fg, mEngine, view, {
.shadows = blackboard.get<FrameGraphTexture>("shadows"),
.ssao = blackboard.get<FrameGraphTexture>("ssao"),
.ssr = ssrConfig.ssr,
.structure = structure
},
config, ssrConfig, colorGradingConfigForColor,
pass, firstRefractionCommand);
config, ssrConfig, colorGradingConfigForColor, pass);
hasScreenSpaceRefraction = output.has_value();
if (hasScreenSpaceRefraction) {
colorPassOutput = output.value();
}
}
if (colorGradingConfig.customResolve) {

View File

@@ -14,12 +14,15 @@ def _compare_goldens(base_dir, comparison_dir, out_dir=None):
for f in all_files)
all_results = []
for test_dir in test_dirs:
results_meta = {}
results = []
output_test_dir = None if not out_dir else os.path.abspath(os.path.join(out_dir, test_dir))
output_test_dir = None if not out_dir else os.path.join(out_dir, test_dir)
if output_test_dir:
mkdir_p(output_test_dir)
base_test_dir = os.path.abspath(os.path.join(base_dir, test_dir))
comp_test_dir = os.path.abspath(os.path.join(comparison_dir, test_dir))
results_meta['base_dir'] = base_test_dir
results_meta['comparison_dir'] = comp_test_dir
for golden_file in \
glob.glob(os.path.join(base_test_dir, "*.tif")):
base_fname = os.path.abspath(golden_file)
@@ -42,12 +45,8 @@ def _compare_goldens(base_dir, comparison_dir, out_dir=None):
result['result'] = RESULT_OK
results.append(result)
if output_test_dir:
results_meta['results'] = results
output_fname = os.path.join(output_test_dir, "compare_results.json")
results_meta = {
'results': results,
'base_dir': os.path.relpath(output_fname, base_test_dir),
'comparison_dir': os.path.relpath(output_fname, comp_test_dir),
}
with open(output_fname, 'w') as f:
f.write(json.dumps(results_meta, indent=2))
important_print(f'Written comparison results for {test_dir} to \n {output_fname}')
@@ -65,7 +64,7 @@ if __name__ == '__main__':
dest = args.dest
if not dest:
print('Assume the default renderdiff output folder')
dest = os.path.join(os.getcwd(), './out/renderdiff')
dest = os.path.join(os.getcwd(), './out/renderdiff_tests')
assert os.path.exists(dest), f"Destination folder={dest} does not exist."
results = _compare_goldens(args.src, dest, out_dir=args.out)

View File

@@ -17,9 +17,6 @@ import sys
import flask
import pathlib
import json
import requests
import io
import zipfile
from utils import ArgParseImpl
@@ -28,163 +25,14 @@ from flask import Flask, request, make_response, send_from_directory
DIR = pathlib.Path(__file__).parent.absolute()
HTML_DIR = os.path.join(DIR, "viewer_html")
# Generated by gemini
def _download_github_artifacts(pr_number, github_token, output_dir= ".") -> None:
"""
Downloads artifacts associated with a specific GitHub Pull Request.
This function performs the following steps:
1. Fetches the details of the Pull Request to get its head commit SHA.
2. Searches for GitHub Actions workflow runs triggered by that specific commit.
3. Iterates through successful workflow runs to find and list all associated artifacts.
4. Downloads each artifact (which comes as a ZIP file).
5. Extracts the contents of each downloaded ZIP file into a unique subdirectory
within the specified output directory.
Args:
owner (str): The GitHub repository owner (e.g., "octocat").
repo (str): The GitHub repository name (e.g., "Spoon-Knife").
pr_number (int): The Pull Request number.
output_dir (str): The local directory where downloaded artifacts will be saved.
Defaults to the current directory.
"""
# Prepare HTTP headers for GitHub API requests
headers = {"Accept": "application/vnd.github.v3+json"}
if github_token:
headers["Authorization"] = f"token {github_token}"
OWNER_REPO = 'google/filament'
# --- Step 1: Get PR details to find the head commit SHA ---
print(f"Fetching details for PR #{pr_number} in {OWNER_REPO}...")
pr_url = f"https://api.github.com/repos/{OWNER_REPO}/pulls/{pr_number}"
try:
response = requests.get(pr_url, headers=headers)
response.raise_for_status() # Raise an exception for HTTP errors (4xx or 5xx)
pr_data = response.json()
commit_sha = pr_data["head"]["sha"]
print(f"PR #{pr_number} is associated with commit SHA: {commit_sha}")
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
print(f"Error: PR #{pr_number} not found in {OWNER_REPO}. Please check the PR number, owner, and repository name.")
elif e.response.status_code == 403:
print(f"Error: Access forbidden to PR #{pr_number}. You might be hitting API rate limits or need a valid GitHub Token.")
else:
print(f"An HTTP error occurred while fetching PR details: {e}")
return # Exit function on error
except requests.exceptions.RequestException as e:
print(f"A network error occurred while fetching PR details: {e}")
return # Exit function on error
# --- Step 2: Find workflow runs associated with the commit SHA ---
print(f"Searching for workflow runs for commit SHA: {commit_sha}...")
workflow_runs_url = f"https://api.github.com/repos/{OWNER_REPO}/actions/runs"
# Filter by head_sha and event='pull_request' for precision
params = {"head_sha": commit_sha, "event": "pull_request"}
try:
response = requests.get(workflow_runs_url, headers=headers, params=params)
response.raise_for_status()
runs_data = response.json()
workflow_runs = runs_data.get("workflow_runs", [])
if not workflow_runs:
print(f"No workflow runs found directly associated with PR #{pr_number} (commit SHA: {commit_sha}).")
print("This might happen if the workflow was triggered by a push after the PR was opened,")
print("or if the PR head branch was updated without triggering a new workflow run with this exact SHA.")
print("Consider checking GitHub Actions runs manually for this PR's branch on GitHub.")
return None
# Filter for runs that completed successfully
successful_runs = [run for run in workflow_runs if run.get("conclusion") == "success" and run.get("status") == "completed"]
if not successful_runs:
print(f"No *successful and completed* workflow runs found for PR #{pr_number} with commit SHA {commit_sha}. Exiting.")
return None
except requests.exceptions.HTTPError as e:
print(f"An HTTP error occurred while searching for workflow runs: {e}")
return None
except requests.exceptions.RequestException as e:
print(f"A network error occurred while searching for workflow runs: {e}")
return None
# Create the main output directory if it doesn't exist
os.makedirs(output_dir, exist_ok=True)
print(f"Ensuring output directory exists: {os.path.abspath(output_dir)}")
downloaded_any_artifact = False # Flag to track if any artifact was downloaded
# --- Step 3 & 4: List and Download Artifacts for each successful run ---
for run in successful_runs:
run_id = run["id"]
run_name = run["name"]
print(f"\nProcessing workflow run '{run_name}' (ID: {run_id})...")
artifacts_url = f"https://api.github.com/repos/{OWNER_REPO}/actions/runs/{run_id}/artifacts"
try:
response = requests.get(artifacts_url, headers=headers)
response.raise_for_status()
artifacts_data = response.json()
artifacts = artifacts_data.get("artifacts", [])
if not artifacts:
print(f" No artifacts found for workflow run ID {run_id}.")
continue # Move to the next workflow run
for artifact in artifacts:
artifact_id = artifact["id"]
artifact_name = artifact["name"]
archive_download_url = artifact["archive_download_url"]
print(f" Found artifact: '{artifact_name}' (ID: {artifact_id})")
# Perform the download request
print(f" Downloading '{artifact_name}'...")
# Use a copy of headers and specific Accept for ZIP download
download_headers = headers.copy()
download_headers["Accept"] = "application/vnd.github.v3+zip"
download_response = requests.get(archive_download_url, headers=download_headers, stream=True)
download_response.raise_for_status() # Check for errors in download
# --- Step 5: Extract the contents ---
# Use BytesIO to handle the zip file content in memory without saving to a temporary file
with io.BytesIO(download_response.content) as zip_buffer:
try:
with zipfile.ZipFile(zip_buffer, 'r') as zip_ref:
# Create a unique subdirectory for each artifact to avoid file name conflicts
extract_path = os.path.join(output_dir, f"{artifact_name}_{artifact_id}")
os.makedirs(extract_path, exist_ok=True)
zip_ref.extractall(extract_path)
print(f" Successfully extracted '{artifact_name}' to '{extract_path}/'")
downloaded_any_artifact = True
except zipfile.BadZipFile:
print(f" Error: Downloaded file for '{artifact_name}' is not a valid zip file. Skipping extraction.")
except Exception as e:
print(f" An error occurred during extraction of '{artifact_name}': {e}")
except requests.exceptions.HTTPError as e:
print(f" An HTTP error occurred while fetching artifacts for run {run_id}: {e}")
if e.response.status_code == 403:
print(" This often means you need a GitHub Personal Access Token with 'repo' scope (even for public repos for artifact downloads).")
continue # Continue processing the next workflow run
except requests.exceptions.RequestException as e:
print(f" A network error occurred while fetching artifacts for run {run_id}: {e}")
continue # Continue processing the next workflow run
if not downloaded_any_artifact:
print("\nNo artifacts were downloaded for the specified PR.")
else:
print("\nAll available artifacts have been processed.")
return 'Done'
def _create_app(config):
app = Flask(__name__)
client_config = config.copy()
base_dir = client_config['base_dir']
comparison_dir = client_config['comparison_dir']
diff_dir = client_config['diff_dir']
base_dir = os.path.join(diff_dir, client_config['base_dir'])
comparison_dir = os.path.join(diff_dir, client_config['comparison_dir'])
del client_config['base_dir']
del client_config['comparison_dir']
del client_config['diff_dir']
@@ -219,37 +67,12 @@ def _create_app(config):
if __name__ == '__main__':
PORT = 8901
parser = ArgParseImpl()
parser.add_argument('--diff', type=str, help='Diff result directory')
parser.add_argument('--pr_number', type=str, help='Pull request artifacts to examine')
parser.add_argument('--github_token', type=str, help='Necessary for pull PR artifacts')
parser.add_argument('--diff', help='Diff directory', required=True)
args, _ = parser.parse_known_args(sys.argv[1:])
if not args.diff and not args.pr_number:
print('Need to specify either a diff result directory or a Pull Request number')
exit(1)
if args.diff and args.pr_number:
print('Cannot specify both a diff result directory and a Pull Request number')
exit(1)
fdir = args.diff
if args.pr_number:
if not args.github_token:
print('Must provide --github_token to be able to download artifacts')
exit(1)
output_dir = f'/tmp/filament-pr{args.pr_number}-rdiff-result'
res = _download_github_artifacts(args.pr_number, args.github_token, output_dir)
if not res:
print('Failed to retrieve PR artifacts')
exit(1)
# TODO: Clean up the following so that we're not so specific on the paths diffs/presubmit
directory_name = list(os.listdir(output_dir))[0]
fdir = os.path.join(os.path.join(output_dir, directory_name), 'diffs/presubmit')
with open(os.path.join(fdir, 'compare_results.json'), 'r') as f:
with open(os.path.join(args.diff, 'compare_results.json'), 'r') as f:
config = json.loads(f.read())
config['diff_dir'] = os.path.abspath(fdir)
config['diff_dir'] = os.path.abspath(args.diff)
app = _create_app(config)
from waitress import serve