This commit is contained in:
elwrcl committed 2026-06-06 13:25:15 +03:00
1 parent 04d4dc8b09
commit 818cab85dc
12511 files changed
+6156641

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
BasedOnStyle: InheritParentConfig
DisableFormat: false
AlignConsecutiveBitFields: true
ColumnLimit: 80
BreakStringLiterals: false
SpaceBeforeParens: ControlStatementsExceptControlMacros
+63
View File
@@ -0,0 +1,63 @@
# Copyright 2025 LunarG, Inc.
# Copyright 2025 Google LLC
# SPDX-License-Identifier: MIT
mtl_bridge_files = files(
'vk_to_mtl_map.h',
'vk_to_mtl_map.c',
'mtl_format.h',
)
if host_machine.system() == 'darwin'
mtl_bridge_files += files(
'mtl_bridge.m',
'mtl_buffer.m',
'mtl_command_buffer.m',
'mtl_command_queue.m',
'mtl_compute_state.m',
'mtl_device.m',
'mtl_encoder.m',
'mtl_heap.m',
'mtl_library.m',
'mtl_render_state.m',
'mtl_residency_set.m',
'mtl_sampler.m',
'mtl_sync.m',
'mtl_texture.m',
)
else
mtl_bridge_files += files(
'stubs/mtl_bridge.c',
'stubs/mtl_buffer.c',
'stubs/mtl_command_buffer.c',
'stubs/mtl_command_queue.c',
'stubs/mtl_compute_state.c',
'stubs/mtl_device.c',
'stubs/mtl_encoder.c',
'stubs/mtl_heap.c',
'stubs/mtl_library.c',
'stubs/mtl_render_state.c',
'stubs/mtl_residency_set.c',
'stubs/mtl_sampler.c',
'stubs/mtl_sync.c',
'stubs/mtl_texture.c',
)
endif
mtl_bridge_dependencies = [
idep_vulkan_lite_runtime_headers,
idep_vulkan_util
]
libmtl_bridge = static_library(
'mtl_bridge',
[mtl_bridge_files],
include_directories : [include_directories('../vulkan/'), inc_include, inc_src],
dependencies : mtl_bridge_dependencies,
gnu_symbol_visibility: 'hidden',
build_by_default: false,
)
idep_mtl_bridge = declare_dependency(
link_with : libmtl_bridge,
)
+44
View File
@@ -0,0 +1,44 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_BRIDGE_H
#define KK_BRIDGE_H 1
/* C wrappers for Metal. May not be complete. If you find something you need
* feel free to add them where they belong. As a rule of thumb, member functions
* go in the objects' .h/.m/.c Naming convention for wrappers is:
* object_type* mtl_new_object_type(params...);
* void mtl_member_function(object_type* ptr, params...);
* void mtl_object_set_member(object_type* ptr, member_type value);
* member_type mtl_object_get_member(object_type* ptr);
*
* Functions that have new in the name require to release the returned object
* via mtl_release(object);
* */
#include "mtl_types.h"
#include "mtl_buffer.h"
#include "mtl_command_buffer.h"
#include "mtl_command_queue.h"
#include "mtl_compute_state.h"
#include "mtl_device.h"
#include "mtl_encoder.h"
#include "mtl_format.h"
#include "mtl_heap.h"
#include "mtl_library.h"
#include "mtl_render_state.h"
#include "mtl_residency_set.h"
#include "mtl_sampler.h"
#include "mtl_sync.h"
#include "mtl_texture.h"
mtl_texture *mtl_drawable_get_texture(void *drawable_ptr);
void *mtl_retain(void *handle);
void mtl_release(void *handle);
#endif /* KK_BRIDGE_H */
+50
View File
@@ -0,0 +1,50 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_bridge.h"
// kk_image_layout.h should also includes "vulkan/vulkan.h", but just to be safe
#include "vulkan/vulkan.h"
#include "kk_image_layout.h"
#include "util/macros.h"
#include <Metal/MTLCommandBuffer.h>
#include <Metal/MTLCommandQueue.h>
#include <Metal/MTLDevice.h>
#include <Metal/MTLHeap.h>
#include <Metal/MTLEvent.h>
#include <QuartzCore/CAMetalLayer.h>
static_assert(sizeof(MTLResourceID) == sizeof(uint64_t), "Must match, otherwise descriptors are broken");
mtl_texture *
mtl_drawable_get_texture(void *drawable_ptr)
{
@autoreleasepool {
id<CAMetalDrawable> drawable = (id<CAMetalDrawable>)drawable_ptr;
return drawable.texture;
}
}
void *
mtl_retain(void *handle)
{
@autoreleasepool {
NSObject *obj = (NSObject *)handle;
return [obj retain];
}
}
void
mtl_release(void *handle)
{
@autoreleasepool {
NSObject *obj = (NSObject *)handle;
[obj release];
}
}
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef MTL_BUFFER_H
#define MTL_BUFFER_H 1
#include "mtl_types.h"
#include <inttypes.h>
struct kk_image_layout;
/* Utils */
uint64_t mtl_buffer_get_length(mtl_buffer *buffer);
uint64_t mtl_buffer_get_gpu_address(mtl_buffer *buffer);
/* Gets CPU address */
void *mtl_get_contents(mtl_buffer *buffer);
/* Allocation from buffer */
mtl_texture *mtl_new_texture_with_descriptor_linear(
mtl_buffer *buffer, const struct kk_image_layout *layout, uint64_t offset);
#endif /* MTL_BUFFER_H */
+78
View File
@@ -0,0 +1,78 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_buffer.h"
/* TODO_KOSMICKRISP Remove */
#include "kk_image_layout.h"
#include <Metal/MTLBuffer.h>
#include <Metal/MTLTexture.h>
uint64_t
mtl_buffer_get_length(mtl_buffer *buffer)
{
@autoreleasepool {
id<MTLBuffer> buf = (id<MTLBuffer>)buffer;
return buf.length;
}
}
uint64_t
mtl_buffer_get_gpu_address(mtl_buffer *buffer)
{
@autoreleasepool {
id<MTLBuffer> buf = (id<MTLBuffer>)buffer;
return [buf gpuAddress];
}
}
void *
mtl_get_contents(mtl_buffer *buffer)
{
@autoreleasepool {
id<MTLBuffer> buf = (id<MTLBuffer>)buffer;
return [buf contents];
}
}
/* TODO_KOSMICKRISP This is a duplicate, but both should be removed once we move kk_image_layout to the bridge. */
static MTLTextureDescriptor *
mtl_new_texture_descriptor(const struct kk_image_layout *layout)
{
@autoreleasepool {
MTLTextureDescriptor *descriptor = [MTLTextureDescriptor new];
descriptor.textureType = (MTLTextureType)layout->type;
descriptor.pixelFormat = layout->format.mtl;
descriptor.width = layout->width_px;
descriptor.height = layout->height_px;
descriptor.depth = layout->depth_px;
descriptor.mipmapLevelCount = layout->levels;
descriptor.sampleCount = layout->sample_count_sa;
descriptor.arrayLength = layout->layers;
descriptor.allowGPUOptimizedContents = layout->optimized_layout;
descriptor.usage = (MTLTextureUsage)layout->usage;
/* We don't set the swizzle because Metal complains when the usage has store or render target with swizzle... */
return descriptor;
}
}
mtl_texture *
mtl_new_texture_with_descriptor_linear(mtl_buffer *buffer,
const struct kk_image_layout *layout,
uint64_t offset)
{
@autoreleasepool {
id<MTLBuffer> buf = (id<MTLBuffer>)buffer;
MTLTextureDescriptor *descriptor = [mtl_new_texture_descriptor(layout) autorelease];
descriptor.resourceOptions = buf.resourceOptions;
id<MTLTexture> texture = [buf newTextureWithDescriptor:descriptor offset:offset bytesPerRow:layout->linear_stride_B];
return texture;
}
}
@@ -0,0 +1,27 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef MTL_COMMAND_BUFFER_H
#define MTL_COMMAND_BUFFER_H 1
#include "mtl_types.h"
#include <stdint.h>
void mtl_encode_signal_event(mtl_command_buffer *cmd_buf_handle,
mtl_event *event_handle, uint64_t value);
void mtl_encode_wait_for_event(mtl_command_buffer *cmd_buf_handle,
mtl_event *event_handle, uint64_t value);
void mtl_add_completed_handler(mtl_command_buffer *cmd,
void (*callback)(void *data), void *data);
void mtl_command_buffer_commit(mtl_command_buffer *cmd_buf);
void mtl_present_drawable(mtl_command_buffer *cmd_buf, void *drawable);
#endif /* MTL_COMMAND_BUFFER_H */
@@ -0,0 +1,66 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_command_buffer.h"
#include <Metal/MTLCommandBuffer.h>
#include <QuartzCore/CAMetalLayer.h>
void
mtl_encode_signal_event(mtl_command_buffer *cmd_buf_handle,
mtl_event *event_handle, uint64_t value)
{
@autoreleasepool {
id<MTLCommandBuffer> cmd_buf = (id<MTLCommandBuffer>)cmd_buf_handle;
id<MTLEvent> event = (id<MTLEvent>)event_handle;
[cmd_buf encodeSignalEvent:event value:value];
}
}
void
mtl_encode_wait_for_event(mtl_command_buffer *cmd_buf_handle,
mtl_event *event_handle, uint64_t value)
{
@autoreleasepool {
id<MTLCommandBuffer> cmd_buf = (id<MTLCommandBuffer>)cmd_buf_handle;
id<MTLEvent> event = (id<MTLEvent>)event_handle;
[cmd_buf encodeWaitForEvent:event value:value];
}
}
void
mtl_add_completed_handler(mtl_command_buffer *cmd, void (*callback)(void *data),
void *data)
{
@autoreleasepool {
id<MTLCommandBuffer> mtl_cmd = (id<MTLCommandBuffer>)cmd;
[mtl_cmd addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull cmd_buf) {
@autoreleasepool {
if (callback)
callback(data);
}
}];
}
}
void
mtl_command_buffer_commit(mtl_command_buffer *cmd_buffer)
{
@autoreleasepool {
id<MTLCommandBuffer> cmd_buf = (id<MTLCommandBuffer>)cmd_buffer;
[cmd_buf commit];
}
}
void
mtl_present_drawable(mtl_command_buffer *cmd_buf, void *drawable_ptr)
{
@autoreleasepool {
id<MTLCommandBuffer> cmd = (id<MTLCommandBuffer>)cmd_buf;
id<CAMetalDrawable> drawable = [(id<CAMetalDrawable>)drawable_ptr autorelease];
[cmd presentDrawable:drawable];
}
}
@@ -0,0 +1,25 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef MTL_COMMAND_QUEUE_H
#define MTL_COMMAND_QUEUE_H 1
#include "mtl_types.h"
#include <stdint.h>
mtl_command_queue *mtl_new_command_queue(mtl_device *device,
uint32_t cmd_buffer_count);
mtl_command_buffer *mtl_new_command_buffer(mtl_command_queue *cmd_queue);
void mtl_command_queue_add_residency_set(mtl_command_queue *cmd_queue,
mtl_residency_set *set);
void mtl_command_queue_remove_residency_set(mtl_command_queue *cmd_queue,
mtl_residency_set *set);
#endif /* MTL_COMMAND_QUEUE_H */
@@ -0,0 +1,50 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_command_queue.h"
#include <Metal/MTLDevice.h>
#include <Metal/MTLCommandQueue.h>
mtl_command_queue *
mtl_new_command_queue(mtl_device *device, uint32_t cmd_buffer_count)
{
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
return [dev newCommandQueueWithMaxCommandBufferCount:cmd_buffer_count];
}
}
mtl_command_buffer *
mtl_new_command_buffer(mtl_command_queue *cmd_queue)
{
@autoreleasepool {
id<MTLCommandQueue> queue = (id<MTLCommandQueue>)cmd_queue;
return [[queue commandBuffer] retain];
}
}
void
mtl_command_queue_add_residency_set(mtl_command_queue *cmd_queue,
mtl_residency_set *set)
{
@autoreleasepool {
id<MTLCommandQueue> queue = (id<MTLCommandQueue>)cmd_queue;
id<MTLResidencySet> s = (id<MTLResidencySet>)set;
return [queue addResidencySet:s];
}
}
void
mtl_command_queue_remove_residency_set(mtl_command_queue *cmd_queue,
mtl_residency_set *set)
{
@autoreleasepool {
id<MTLCommandQueue> queue = (id<MTLCommandQueue>)cmd_queue;
id<MTLResidencySet> s = (id<MTLResidencySet>)set;
return [queue removeResidencySet:s];
}
}
@@ -0,0 +1,13 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_types.h"
#include <stdint.h>
mtl_compute_pipeline_state *
mtl_new_compute_pipeline_state(mtl_device *device, mtl_function *function,
uint64_t max_total_threads_per_threadgroup);
@@ -0,0 +1,29 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_compute_state.h"
#include <Metal/MTLComputePipeline.h>
mtl_compute_pipeline_state *
mtl_new_compute_pipeline_state(mtl_device *device, mtl_function *function,
uint64_t max_total_threads_per_threadgroup)
{
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
id<MTLComputePipelineState> pipeline = NULL;
MTLComputePipelineDescriptor *comp_desc = [[[MTLComputePipelineDescriptor alloc] init] autorelease];
NSError *error;
comp_desc.computeFunction = (id<MTLFunction>)function;
comp_desc.maxTotalThreadsPerThreadgroup = max_total_threads_per_threadgroup;
pipeline = [dev newComputePipelineStateWithDescriptor:comp_desc options:0 reflection:nil error:&error];
/* TODO_KOSMICKRISP Error checking */
return pipeline;
}
}
+63
View File
@@ -0,0 +1,63 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef MTL_DEVICE_H
#define MTL_DEVICE_H 1
#include "mtl_format.h"
#include "mtl_types.h"
#include <stdbool.h>
#include <stdint.h>
/* TODO_KOSMICKRISP Remove */
struct kk_image_layout;
/* Device creation */
mtl_device *mtl_device_create(void);
/* Device operations */
void mtl_start_gpu_capture(mtl_device *mtl_dev_handle, const char *directory);
void mtl_stop_gpu_capture(void);
/* Device feature query */
void mtl_device_get_name(mtl_device *dev, char buffer[256]);
void mtl_device_get_architecture_name(mtl_device *dev, char buffer[256]);
uint64_t mtl_device_get_peer_group_id(mtl_device *dev);
uint32_t mtl_device_get_peer_index(mtl_device *dev);
uint64_t mtl_device_get_registry_id(mtl_device *dev);
bool mtl_device_supports_sample_count(mtl_device *dev, uint32_t sample_count);
struct mtl_size mtl_device_max_threads_per_threadgroup(mtl_device *dev);
uint32_t mtl_device_max_threadgroup_memory_length(mtl_device *dev);
uint64_t mtl_device_max_buffer_length(mtl_device *dev);
uint64_t mtl_device_recommended_max_working_set_size(mtl_device *dev);
uint64_t mtl_device_current_allocated_size(mtl_device *dev);
/* Timestamp query */
uint64_t mtl_device_get_gpu_timestamp(mtl_device *dev);
/* Resource queries */
void mtl_heap_buffer_size_and_align_with_length(mtl_device *device,
uint64_t *size_B,
uint64_t *align_B);
uint64_t mtl_minimum_linear_texture_alignment_for_pixel_format(
mtl_device *device, enum mtl_pixel_format format);
void mtl_heap_texture_size_and_align_with_descriptor(
mtl_device *device, struct kk_image_layout *layout, uint64_t *size_B,
uint64_t *align_B);
uint32_t mtl_sparse_tile_size_in_bytes(mtl_device *device);
struct mtl_size mtl_sparse_tile_size(mtl_device *device,
struct kk_image_layout *layout);
struct mtl_size mtl_sparse_tile_count(mtl_device *device,
struct kk_image_layout *layout,
struct mtl_size tile_size);
/* Resource creation */
mtl_buffer *mtl_new_buffer_with_bytes_no_copy(mtl_device *device, void *ptr,
uint64_t size_B);
#endif /* MTL_DEVICE_H */
+303
View File
@@ -0,0 +1,303 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_device.h"
/* TODO_KOSMICKRISP Remove */
#include "kk_image_layout.h"
#include "kk_private.h"
#include <Metal/MTLDevice.h>
#include <Metal/MTLCaptureManager.h>
/* Device creation */
mtl_device *
mtl_device_create()
{
mtl_device *device = 0u;
@autoreleasepool {
NSArray<id<MTLDevice>> *devs = [MTLCopyAllDevices() autorelease];
uint32_t device_count = [devs count];
for (uint32_t i = 0u; i < device_count; ++i) {
if (@available(macOS 10.15, *)) {
if (!device && [devs[i] supportsFamily:MTLGPUFamilyMetal3]) {
device = (mtl_device *)[devs[i] retain];
break;
}
}
}
}
return device;
}
/* Device operations */
void
mtl_start_gpu_capture(mtl_device *mtl_dev_handle, const char *directory)
{
@autoreleasepool {
id<MTLDevice> mtl_dev = (id<MTLDevice>)mtl_dev_handle;
MTLCaptureManager *captureMgr = [MTLCaptureManager sharedCaptureManager];
MTLCaptureDescriptor *captureDesc = [[MTLCaptureDescriptor new] autorelease];
captureDesc.captureObject = mtl_dev;
captureDesc.destination = MTLCaptureDestinationDeveloperTools;
if (directory && [captureMgr supportsDestination: MTLCaptureDestinationGPUTraceDocument]) {
NSString *dir = [NSString stringWithUTF8String:directory];
NSString *pname = [[NSProcessInfo processInfo] processName];
NSString *capture_path = [NSString stringWithFormat:@"%@/%@.gputrace", dir, pname];
captureDesc.destination = MTLCaptureDestinationGPUTraceDocument;
captureDesc.outputURL = [NSURL fileURLWithPath: capture_path];
}
NSError *err = nil;
if (![captureMgr startCaptureWithDescriptor:captureDesc error:&err]) {
fprintf(stderr, "Failed to automatically start GPU capture session (Error code %li) using startCaptureWithDescriptor: %s\n",
(long)err.code, err.localizedDescription.UTF8String);
}
}
}
void
mtl_stop_gpu_capture()
{
@autoreleasepool {
[[MTLCaptureManager sharedCaptureManager] stopCapture];
}
}
/* Device feature query */
void
mtl_device_get_name(mtl_device *dev, char buffer[256])
{
@autoreleasepool {
id<MTLDevice> device = (id<MTLDevice>)dev;
[device.name getCString:buffer maxLength:(sizeof(char) * 256) encoding:NSUTF8StringEncoding];
}
}
void
mtl_device_get_architecture_name(mtl_device *dev, char buffer[256])
{
@autoreleasepool {
id<MTLDevice> device = (id<MTLDevice>)dev;
[device.architecture.name getCString:buffer maxLength:(sizeof(char) * 256) encoding:NSUTF8StringEncoding];
}
}
uint64_t
mtl_device_get_peer_group_id(mtl_device *dev)
{
@autoreleasepool {
id<MTLDevice> device = (id<MTLDevice>)dev;
return device.peerGroupID;
}
}
uint32_t
mtl_device_get_peer_index(mtl_device *dev)
{
@autoreleasepool {
id<MTLDevice> device = (id<MTLDevice>)dev;
return device.peerIndex;
}
}
uint64_t
mtl_device_get_registry_id(mtl_device *dev)
{
@autoreleasepool {
id<MTLDevice> device = (id<MTLDevice>)dev;
return device.registryID;
}
}
bool
mtl_device_supports_sample_count(mtl_device *dev, uint32_t sample_count)
{
@autoreleasepool {
id<MTLDevice> device = (id<MTLDevice>)dev;
return [device supportsTextureSampleCount:sample_count];
}
}
struct mtl_size
mtl_device_max_threads_per_threadgroup(mtl_device *dev)
{
@autoreleasepool {
id<MTLDevice> device = (id<MTLDevice>)dev;
return (struct mtl_size){.x = device.maxThreadsPerThreadgroup.width,
.y = device.maxThreadsPerThreadgroup.height,
.z = device.maxThreadsPerThreadgroup.depth};
}
}
uint32_t
mtl_device_max_threadgroup_memory_length(mtl_device *dev)
{
@autoreleasepool {
id<MTLDevice> device = (id<MTLDevice>)dev;
return device.maxThreadgroupMemoryLength;
}
}
uint64_t
mtl_device_max_buffer_length(mtl_device *dev)
{
@autoreleasepool {
id<MTLDevice> device = (id<MTLDevice>)dev;
return device.maxBufferLength;
}
}
uint64_t
mtl_device_recommended_max_working_set_size(mtl_device *dev)
{
@autoreleasepool {
id<MTLDevice> device = (id<MTLDevice>)dev;
return device.recommendedMaxWorkingSetSize;
}
}
uint64_t
mtl_device_current_allocated_size(mtl_device *dev)
{
@autoreleasepool {
id<MTLDevice> device = (id<MTLDevice>)dev;
return device.currentAllocatedSize;
}
}
/* Timestamp query */
uint64_t
mtl_device_get_gpu_timestamp(mtl_device *dev)
{
@autoreleasepool {
id<MTLDevice> device = (id<MTLDevice>)dev;
MTLTimestamp cpu_ts, gpu_ts;
[device sampleTimestamps:&cpu_ts gpuTimestamp:&gpu_ts];
return (uint64_t)gpu_ts;
}
}
/* Resource queries */
/* TODO_KOSMICKRISP Return a struct */
void
mtl_heap_buffer_size_and_align_with_length(mtl_device *device, uint64_t *size_B,
uint64_t *align_B)
{
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
MTLSizeAndAlign size_align = [dev heapBufferSizeAndAlignWithLength:*size_B options:KK_MTL_RESOURCE_OPTIONS];
*size_B = size_align.size;
*align_B = size_align.align;
}
}
/* TODO_KOSMICKRISP Remove */
static MTLTextureDescriptor *
mtl_new_texture_descriptor(const struct kk_image_layout *layout)
{
@autoreleasepool {
MTLTextureDescriptor *descriptor = [MTLTextureDescriptor new];
descriptor.textureType = (MTLTextureType)layout->type;
descriptor.pixelFormat = layout->format.mtl;
descriptor.width = layout->width_px;
descriptor.height = layout->height_px;
descriptor.depth = layout->depth_px;
descriptor.mipmapLevelCount = layout->levels;
descriptor.sampleCount = layout->sample_count_sa;
descriptor.arrayLength = layout->layers;
descriptor.allowGPUOptimizedContents = layout->optimized_layout;
descriptor.usage = (MTLTextureUsage)layout->usage;
/* We don't set the swizzle because Metal complains when the usage has store or render target with swizzle... */
return descriptor;
}
}
uint64_t
mtl_minimum_linear_texture_alignment_for_pixel_format(
mtl_device *device, enum mtl_pixel_format format)
{
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
return [dev minimumLinearTextureAlignmentForPixelFormat:(MTLPixelFormat)format];
}
}
void
mtl_heap_texture_size_and_align_with_descriptor(mtl_device *device,
struct kk_image_layout *layout,
uint64_t *size_B,
uint64_t *align_B)
{
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
MTLTextureDescriptor *descriptor = [mtl_new_texture_descriptor(layout) autorelease];
descriptor.resourceOptions = KK_MTL_RESOURCE_OPTIONS;
MTLSizeAndAlign size_align = [dev heapTextureSizeAndAlignWithDescriptor:descriptor];
if (size_B)
*size_B = size_align.size;
if (align_B)
*align_B = size_align.align;
}
}
uint32_t
mtl_sparse_tile_size_in_bytes(mtl_device *device) {
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
return [dev sparseTileSizeInBytes];
}
}
struct mtl_size
mtl_sparse_tile_size(mtl_device *device, struct kk_image_layout *layout) {
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
MTLSize tile_size = [dev sparseTileSizeWithTextureType:(MTLTextureType)layout->type
pixelFormat:layout->format.mtl
sampleCount:layout->sample_count_sa];
return (struct mtl_size){tile_size.width, tile_size.height, tile_size.depth};
}
}
struct mtl_size
mtl_sparse_tile_count(mtl_device *device, struct kk_image_layout *layout,
struct mtl_size tile_size) {
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
MTLRegion pixel_region = MTLRegionMake3D(
0, 0, 0, layout->width_px, layout->height_px, layout->depth_px);
MTLRegion tile_region;
[dev convertSparsePixelRegions:&pixel_region
toTileRegions:&tile_region
withTileSize:MTLSizeMake(tile_size.x, tile_size.y, tile_size.z)
alignmentMode:MTLSparseTextureRegionAlignmentModeOutward
numRegions:1];
return (struct mtl_size){tile_region.size.width, tile_region.size.height,
tile_region.size.depth};
}
}
/* Resource creation */
mtl_buffer *
mtl_new_buffer_with_bytes_no_copy(mtl_device *device, void* ptr,
uint64_t size_B)
{
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
return [dev newBufferWithBytesNoCopy:ptr length:size_B options:KK_MTL_RESOURCE_OPTIONS deallocator:nil];
}
}
+154
View File
@@ -0,0 +1,154 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef MTL_ENCODER_H
#define MTL_ENCODER_H 1
#include "mtl_types.h"
#include <stdint.h>
/* Common encoder utils */
void mtl_end_encoding(void *encoder);
/* MTLBlitEncoder */
mtl_blit_encoder *mtl_new_blit_command_encoder(mtl_command_buffer *cmd_buffer);
void mtl_blit_update_fence(mtl_blit_encoder *encoder, mtl_fence *fence);
void mtl_blit_wait_for_fence(mtl_blit_encoder *encoder, mtl_fence *fence);
void mtl_copy_from_buffer_to_buffer(mtl_blit_encoder *blit_enc_handle,
mtl_buffer *src_buf, size_t src_offset,
mtl_buffer *dst_buf, size_t dst_offset,
size_t size);
void mtl_copy_from_buffer_to_texture(mtl_blit_encoder *blit_enc_handle,
struct mtl_buffer_image_copy *data);
void mtl_copy_from_texture_to_buffer(mtl_blit_encoder *blit_enc_handle,
struct mtl_buffer_image_copy *data);
void mtl_copy_from_texture_to_texture(
mtl_blit_encoder *blit_enc_handle, mtl_texture *src_tex_handle,
size_t src_slice, size_t src_level, struct mtl_origin src_origin,
struct mtl_size src_size, mtl_texture *dst_tex_handle, size_t dst_slice,
size_t dst_level, struct mtl_origin dst_origin);
/* MTLComputeEncoder */
mtl_compute_encoder *
mtl_new_compute_command_encoder(mtl_command_buffer *cmd_buffer);
void mtl_compute_update_fence(mtl_compute_encoder *encoder, mtl_fence *fence);
void mtl_compute_wait_for_fence(mtl_compute_encoder *encoder, mtl_fence *fence);
void mtl_compute_set_pipeline_state(mtl_compute_encoder *encoder,
mtl_compute_pipeline_state *state_handle);
void mtl_compute_set_buffer(mtl_compute_encoder *encoder, mtl_buffer *buffer,
size_t offset, size_t index);
void mtl_compute_use_heaps(mtl_compute_encoder *encoder, mtl_heap **heaps,
uint32_t count);
void mtl_dispatch_threads(mtl_compute_encoder *encoder,
struct mtl_size grid_size,
struct mtl_size local_size);
void mtl_dispatch_threadgroups_with_indirect_buffer(
mtl_compute_encoder *encoder, mtl_buffer *buffer, uint32_t offset,
struct mtl_size local_size);
void mtl_memory_barrier_with_scope(mtl_compute_encoder *encoder,
enum mtl_barrier_scope scope);
/* MTLRenderEncoder */
mtl_render_encoder *mtl_new_render_command_encoder_with_descriptor(
mtl_command_buffer *command_buffer, mtl_render_pass_descriptor *descriptor);
void mtl_render_update_fence(mtl_render_encoder *encoder, mtl_fence *fence);
void mtl_render_wait_for_fence(mtl_render_encoder *encoder, mtl_fence *fence);
void mtl_set_viewports(mtl_render_encoder *encoder,
struct mtl_viewport *viewports, uint32_t count);
void mtl_set_scissor_rects(mtl_render_encoder *encoder,
struct mtl_scissor_rect *scissor_rects,
uint32_t count);
void mtl_render_set_pipeline_state(mtl_render_encoder *encoder,
mtl_render_pipeline_state *pipeline);
void mtl_set_depth_stencil_state(mtl_render_encoder *encoder,
mtl_depth_stencil_state *state);
void mtl_set_stencil_references(mtl_render_encoder *encoder, uint32_t front,
uint32_t back);
void mtl_set_front_face_winding(mtl_render_encoder *encoder,
enum mtl_winding winding);
void mtl_set_cull_mode(mtl_render_encoder *encoder, enum mtl_cull_mode mode);
void mtl_set_visibility_result_mode(mtl_render_encoder *encoder,
enum mtl_visibility_result_mode mode,
size_t offset);
void mtl_set_depth_bias(mtl_render_encoder *encoder, float depth_bias,
float slope_scale, float clamp);
void mtl_set_depth_clip_mode(mtl_render_encoder *encoder,
enum mtl_depth_clip_mode mode);
void mtl_set_vertex_amplification_count(mtl_render_encoder *encoder,
uint32_t *layer_ids, uint32_t id_count);
void mtl_set_vertex_buffer(mtl_render_encoder *encoder, mtl_buffer *buffer,
uint32_t offset, uint32_t index);
void mtl_set_vertex_bytes(mtl_render_encoder *encoder, const void *bytes,
uint32_t length, uint32_t index);
void mtl_set_fragment_buffer(mtl_render_encoder *encoder, mtl_buffer *buffer,
uint32_t offset, uint32_t index);
void mtl_set_fragment_bytes(mtl_render_encoder *encoder, const void *bytes,
uint32_t length, uint32_t index);
void mtl_draw_primitives(mtl_render_encoder *encoder,
enum mtl_primitive_type primitve_type,
uint32_t vertexStart, uint32_t vertexCount,
uint32_t instanceCount, uint32_t baseInstance);
void mtl_draw_indexed_primitives(
mtl_render_encoder *encoder, enum mtl_primitive_type primitve_type,
uint32_t index_count, enum mtl_index_type index_type,
mtl_buffer *index_buffer, uint32_t index_buffer_offset,
uint32_t instance_count, int32_t base_vertex, uint32_t base_instance);
void mtl_draw_primitives_indirect(mtl_render_encoder *encoder,
enum mtl_primitive_type primitve_type,
mtl_buffer *indirect_buffer,
uint64_t indirect_buffer_offset);
void mtl_draw_indexed_primitives_indirect(mtl_render_encoder *encoder,
enum mtl_primitive_type primitve_type,
enum mtl_index_type index_type,
mtl_buffer *index_buffer,
uint32_t index_buffer_offset,
mtl_buffer *indirect_buffer,
uint64_t indirect_buffer_offset);
void mtl_render_use_resource(mtl_compute_encoder *encoder,
mtl_resource *res_handle, uint32_t usage);
void mtl_render_use_resources(mtl_render_encoder *encoder,
mtl_resource **resource_handles, uint32_t count,
enum mtl_resource_usage usage);
void mtl_render_use_heaps(mtl_render_encoder *encoder, mtl_heap **heaps,
uint32_t count);
#endif /* MTL_ENCODER_H */
+541
View File
@@ -0,0 +1,541 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_encoder.h"
#include <Metal/MTLBlitCommandEncoder.h>
#include <Metal/MTLComputeCommandEncoder.h>
#include <Metal/MTLRenderCommandEncoder.h>
/* Common encoder utils */
void
mtl_end_encoding(void *encoder)
{
@autoreleasepool {
id<MTLCommandEncoder> enc = (id<MTLComputeCommandEncoder>)encoder;
[enc endEncoding];
}
}
/* MTLBlitEncoder */
mtl_blit_encoder *
mtl_new_blit_command_encoder(mtl_command_buffer *cmd_buffer)
{
@autoreleasepool {
id<MTLCommandBuffer> cmd_buf = (id<MTLCommandBuffer>)cmd_buffer;
return [[cmd_buf blitCommandEncoder] retain];
}
}
void
mtl_blit_update_fence(mtl_blit_encoder *encoder,
mtl_fence *fence)
{
@autoreleasepool {
id<MTLBlitCommandEncoder> enc = (id<MTLBlitCommandEncoder>)encoder;
id<MTLFence> f = (id<MTLFence>)fence;
[enc updateFence:f];
}
}
void
mtl_blit_wait_for_fence(mtl_blit_encoder *encoder,
mtl_fence *fence)
{
@autoreleasepool {
id<MTLBlitCommandEncoder> enc = (id<MTLBlitCommandEncoder>)encoder;
id<MTLFence> f = (id<MTLFence>)fence;
[enc waitForFence:f];
}
}
void
mtl_copy_from_buffer_to_buffer(mtl_blit_encoder *blit_enc_handle,
mtl_buffer *src_buf, size_t src_offset,
mtl_buffer *dst_buf, size_t dst_offset,
size_t size)
{
@autoreleasepool {
id<MTLBlitCommandEncoder> blit = (id<MTLBlitCommandEncoder>)blit_enc_handle;
id<MTLBuffer> mtl_src_buffer = (id<MTLBuffer>)src_buf;
id<MTLBuffer> mtl_dst_buffer = (id<MTLBuffer>)dst_buf;
[blit copyFromBuffer:mtl_src_buffer sourceOffset:src_offset toBuffer:mtl_dst_buffer destinationOffset:dst_offset size:size];
}
}
void
mtl_copy_from_buffer_to_texture(mtl_blit_encoder *blit_enc_handle,
struct mtl_buffer_image_copy *data)
{
@autoreleasepool {
const MTLSize size = MTLSizeMake(data->image_size.x, data->image_size.y, data->image_size.z);
const MTLOrigin origin = MTLOriginMake(data->image_origin.x, data->image_origin.y, data->image_origin.z);
id<MTLBlitCommandEncoder> blit = (id<MTLBlitCommandEncoder>)blit_enc_handle;
id<MTLBuffer> buffer = (id<MTLBuffer>)data->buffer;
id<MTLTexture> image = (id<MTLTexture>)data->image;
[blit copyFromBuffer:buffer
sourceOffset:data->buffer_offset_B
sourceBytesPerRow:data->buffer_stride_B
sourceBytesPerImage:data->buffer_2d_image_size_B
sourceSize:size
toTexture:image
destinationSlice:data->image_slice
destinationLevel:data->image_level
destinationOrigin:origin
options:(MTLBlitOption)data->options];
}
}
void
mtl_copy_from_texture_to_buffer(mtl_blit_encoder *blit_enc_handle,
struct mtl_buffer_image_copy *data)
{
@autoreleasepool {
const MTLSize size = MTLSizeMake(data->image_size.x, data->image_size.y, data->image_size.z);
const MTLOrigin origin = MTLOriginMake(data->image_origin.x, data->image_origin.y, data->image_origin.z);
id<MTLBlitCommandEncoder> blit = (id<MTLBlitCommandEncoder>)blit_enc_handle;
id<MTLBuffer> buffer = (id<MTLBuffer>)data->buffer;
id<MTLTexture> image = (id<MTLTexture>)data->image;
[blit copyFromTexture:image
sourceSlice:data->image_slice
sourceLevel:data->image_level
sourceOrigin:origin
sourceSize:size
toBuffer:buffer
destinationOffset:data->buffer_offset_B
destinationBytesPerRow:data->buffer_stride_B
destinationBytesPerImage:data->buffer_2d_image_size_B
options:(MTLBlitOption)data->options];
}
}
void
mtl_copy_from_texture_to_texture(mtl_blit_encoder *blit_enc_handle,
mtl_texture *src_tex_handle, size_t src_slice,
size_t src_level, struct mtl_origin src_origin,
struct mtl_size src_size,
mtl_texture *dst_tex_handle, size_t dst_slice,
size_t dst_level, struct mtl_origin dst_origin)
{
@autoreleasepool {
MTLOrigin mtl_src_origin = MTLOriginMake(src_origin.x, src_origin.y, src_origin.z);
MTLSize mtl_src_size = MTLSizeMake(src_size.x, src_size.y, src_size.z);
MTLOrigin mtl_dst_origin = MTLOriginMake(dst_origin.x, dst_origin.y, dst_origin.z);
id<MTLTexture> mtl_dst_tex = (id<MTLTexture>)dst_tex_handle;
id<MTLBlitCommandEncoder> blit = (id<MTLBlitCommandEncoder>)blit_enc_handle;
id<MTLTexture> mtl_src_tex = (id<MTLTexture>)src_tex_handle;
[blit copyFromTexture:mtl_src_tex
sourceSlice:src_slice
sourceLevel:src_level
sourceOrigin:mtl_src_origin
sourceSize:mtl_src_size
toTexture:mtl_dst_tex
destinationSlice:dst_slice
destinationLevel:dst_level
destinationOrigin:mtl_dst_origin];
}
}
/* MTLComputeEncoder */
mtl_compute_encoder *
mtl_new_compute_command_encoder(mtl_command_buffer *cmd_buffer)
{
@autoreleasepool {
id<MTLCommandBuffer> cmd_buf = (id<MTLCommandBuffer>)cmd_buffer;
return [[cmd_buf computeCommandEncoder] retain];
}
}
void
mtl_compute_update_fence(mtl_compute_encoder *encoder, mtl_fence *fence)
{
@autoreleasepool {
id<MTLComputeCommandEncoder> enc = (id<MTLComputeCommandEncoder>)encoder;
id<MTLFence> f = (id<MTLFence>)fence;
[enc updateFence:f];
}
}
void
mtl_compute_wait_for_fence(mtl_compute_encoder *encoder, mtl_fence *fence)
{
@autoreleasepool {
id<MTLComputeCommandEncoder> enc = (id<MTLComputeCommandEncoder>)encoder;
id<MTLFence> f = (id<MTLFence>)fence;
[enc waitForFence:f];
}
}
void
mtl_compute_set_pipeline_state(mtl_compute_encoder *encoder,
mtl_compute_pipeline_state *state_handle)
{
@autoreleasepool {
id<MTLComputeCommandEncoder> enc = (id<MTLComputeCommandEncoder>)encoder;
id<MTLComputePipelineState> state = (id<MTLComputePipelineState>)state_handle;
[enc setComputePipelineState:state];
}
}
void
mtl_compute_set_buffer(mtl_compute_encoder *encoder,
mtl_buffer *buffer, size_t offset, size_t index)
{
@autoreleasepool {
id<MTLComputeCommandEncoder> enc = (id<MTLComputeCommandEncoder>)encoder;
id<MTLBuffer> buf = (id<MTLBuffer>)buffer;
[enc setBuffer:buf offset:offset atIndex:index];
}
}
void
mtl_compute_use_heaps(mtl_compute_encoder *encoder, mtl_heap **heaps,
uint32_t count)
{
@autoreleasepool {
id<MTLComputeCommandEncoder> enc = (id<MTLComputeCommandEncoder>)encoder;
id<MTLHeap> *handles = (id<MTLHeap>*)heaps;
[enc useHeaps:handles count:count];
}
}
void
mtl_dispatch_threads(mtl_compute_encoder *encoder,
struct mtl_size grid_size, struct mtl_size local_size)
{
@autoreleasepool {
id<MTLComputeCommandEncoder> enc = (id<MTLComputeCommandEncoder>)encoder;
MTLSize thread_count = MTLSizeMake(grid_size.x, grid_size.y, grid_size.z);
MTLSize threads_per_threadgroup = MTLSizeMake(local_size.x, local_size.y,
local_size.z);
// TODO_KOSMICKRISP can we rely on nonuniform threadgroup size support?
[enc dispatchThreads:thread_count threadsPerThreadgroup:threads_per_threadgroup];
}
}
void
mtl_dispatch_threadgroups_with_indirect_buffer(mtl_compute_encoder *encoder,
mtl_buffer *buffer,
uint32_t offset,
struct mtl_size local_size)
{
@autoreleasepool {
id<MTLComputeCommandEncoder> enc = (id<MTLComputeCommandEncoder>)encoder;
id<MTLBuffer> buf = (id<MTLBuffer>)buffer;
MTLSize threads_per_threadgroup = MTLSizeMake(local_size.x,
local_size.y,
local_size.z);
[enc dispatchThreadgroupsWithIndirectBuffer:buf indirectBufferOffset:offset threadsPerThreadgroup:threads_per_threadgroup];
}
}
void
mtl_memory_barrier_with_scope(mtl_compute_encoder *encoder,
enum mtl_barrier_scope scope)
{
@autoreleasepool {
id<MTLComputeCommandEncoder> enc = (id<MTLComputeCommandEncoder>)encoder;
[enc memoryBarrierWithScope:(MTLBarrierScope)scope];
}
}
/* MTLRenderEncoder */
/* Encoder commands */
mtl_render_encoder *
mtl_new_render_command_encoder_with_descriptor(
mtl_command_buffer *command_buffer, mtl_render_pass_descriptor *descriptor)
{
@autoreleasepool {
id<MTLCommandBuffer> cmd = (id<MTLCommandBuffer>)command_buffer;
MTLRenderPassDescriptor *desc = (MTLRenderPassDescriptor *)descriptor;
return [[cmd renderCommandEncoderWithDescriptor:desc] retain];
}
}
void
mtl_render_update_fence(mtl_render_encoder *encoder, mtl_fence *fence)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
id<MTLFence> f = (id<MTLFence>)fence;
[enc updateFence:f afterStages:MTLRenderStageFragment];
}
}
void
mtl_render_wait_for_fence(mtl_render_encoder *encoder, mtl_fence *fence)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
id<MTLFence> f = (id<MTLFence>)fence;
[enc waitForFence:f beforeStages:MTLRenderStageVertex];
}
}
void
mtl_set_viewports(mtl_render_encoder *encoder, struct mtl_viewport *viewports,
uint32_t count)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
MTLViewport *vps = (MTLViewport *)viewports;
[enc setViewports:vps count:count];
}
}
void
mtl_set_scissor_rects(mtl_render_encoder *encoder,
struct mtl_scissor_rect *scissor_rects, uint32_t count)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
MTLScissorRect *rects = (MTLScissorRect *)scissor_rects;
[enc setScissorRects:rects count:count];
}
}
void
mtl_render_set_pipeline_state(mtl_render_encoder *encoder,
mtl_render_pipeline_state *pipeline)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
id<MTLRenderPipelineState> pipe = (id<MTLRenderPipelineState>)pipeline;
[enc setRenderPipelineState:pipe];
}
}
void
mtl_set_depth_stencil_state(mtl_render_encoder *encoder,
mtl_depth_stencil_state *state)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
id<MTLDepthStencilState> s = (id<MTLDepthStencilState>)state;
[enc setDepthStencilState:s];
}
}
void
mtl_set_stencil_references(mtl_render_encoder *encoder, uint32_t front,
uint32_t back)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
[enc setStencilFrontReferenceValue:front backReferenceValue:back];
}
}
void
mtl_set_front_face_winding(mtl_render_encoder *encoder,
enum mtl_winding winding)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
[enc setFrontFacingWinding:(MTLWinding)winding];
}
}
void
mtl_set_cull_mode(mtl_render_encoder *encoder, enum mtl_cull_mode mode)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
[enc setCullMode:(MTLCullMode)mode];
}
}
void
mtl_set_visibility_result_mode(mtl_render_encoder *encoder,
enum mtl_visibility_result_mode mode,
size_t offset)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
[enc setVisibilityResultMode:(MTLVisibilityResultMode)mode offset:offset];
}
}
void
mtl_set_depth_bias(mtl_render_encoder *encoder, float depth_bias,
float slope_scale, float clamp)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
[enc setDepthBias:depth_bias slopeScale:slope_scale clamp:clamp];
}
}
void
mtl_set_depth_clip_mode(mtl_render_encoder *encoder,
enum mtl_depth_clip_mode mode)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
[enc setDepthClipMode:(MTLDepthClipMode)mode];
}
}
void
mtl_set_vertex_amplification_count(mtl_render_encoder *encoder,
uint32_t *layer_ids, uint32_t id_count)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
MTLVertexAmplificationViewMapping mappings[32];
for (uint32_t i = 0u; i < id_count; ++i) {
mappings[i].renderTargetArrayIndexOffset = layer_ids[i];
mappings[i].viewportArrayIndexOffset = 0u;
}
[enc setVertexAmplificationCount:id_count viewMappings:mappings];
}
}
void
mtl_set_vertex_buffer(mtl_render_encoder *encoder, mtl_buffer *buffer,
uint32_t offset, uint32_t index)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
id<MTLBuffer> buf = (id<MTLBuffer>)buffer;
[enc setVertexBuffer:buf offset:offset atIndex:index];
}
}
void
mtl_set_vertex_bytes(mtl_render_encoder *encoder, const void *bytes,
uint32_t length, uint32_t index)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
[enc setVertexBytes:bytes length:length atIndex:index];
}
}
void
mtl_set_fragment_buffer(mtl_render_encoder *encoder, mtl_buffer *buffer,
uint32_t offset, uint32_t index)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
id<MTLBuffer> buf = (id<MTLBuffer>)buffer;
[enc setFragmentBuffer:buf offset:offset atIndex:index];
}
}
void
mtl_set_fragment_bytes(mtl_render_encoder *encoder, const void *bytes,
uint32_t length, uint32_t index)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
[enc setFragmentBytes:bytes length:length atIndex:index];
}
}
void
mtl_draw_primitives(mtl_render_encoder *encoder,
enum mtl_primitive_type primitve_type, uint32_t vertexStart,
uint32_t vertexCount, uint32_t instanceCount,
uint32_t baseInstance)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
MTLPrimitiveType type = (MTLPrimitiveType)primitve_type;
[enc drawPrimitives:type vertexStart:vertexStart vertexCount:vertexCount instanceCount:instanceCount baseInstance:baseInstance];
}
}
void
mtl_draw_indexed_primitives(
mtl_render_encoder *encoder, enum mtl_primitive_type primitve_type,
uint32_t index_count, enum mtl_index_type index_type,
mtl_buffer *index_buffer, uint32_t index_buffer_offset,
uint32_t instance_count, int32_t base_vertex, uint32_t base_instance)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
id<MTLBuffer> buf = (id<MTLBuffer>)index_buffer;
MTLIndexType ndx_type = (MTLIndexType)index_type;
MTLPrimitiveType primitive = (MTLPrimitiveType)primitve_type;
[enc drawIndexedPrimitives:primitive indexCount:index_count indexType:ndx_type indexBuffer:buf indexBufferOffset:index_buffer_offset instanceCount:instance_count baseVertex:base_vertex baseInstance:base_instance];
}
}
void
mtl_draw_primitives_indirect(mtl_render_encoder *encoder,
enum mtl_primitive_type primitve_type,
mtl_buffer *indirect_buffer,
uint64_t indirect_buffer_offset)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
id<MTLBuffer> buf = (id<MTLBuffer>)indirect_buffer;
MTLPrimitiveType type = (MTLPrimitiveType)primitve_type;
[enc drawPrimitives:type indirectBuffer:buf indirectBufferOffset:indirect_buffer_offset];
}
}
void
mtl_draw_indexed_primitives_indirect(mtl_render_encoder *encoder,
enum mtl_primitive_type primitve_type,
enum mtl_index_type index_type,
mtl_buffer *index_buffer,
uint32_t index_buffer_offset,
mtl_buffer *indirect_buffer,
uint64_t indirect_buffer_offset)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
id<MTLBuffer> buf = (id<MTLBuffer>)indirect_buffer;
id<MTLBuffer> ndx_buf = (id<MTLBuffer>)index_buffer;
MTLPrimitiveType type = (MTLPrimitiveType)primitve_type;
MTLIndexType ndx_type = (MTLIndexType)index_type;
[enc drawIndexedPrimitives:type indexType:ndx_type indexBuffer:ndx_buf indexBufferOffset:index_buffer_offset indirectBuffer:buf indirectBufferOffset:indirect_buffer_offset];
}
}
void
mtl_render_use_resource(mtl_compute_encoder *encoder, mtl_resource *res_handle,
uint32_t usage)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
id<MTLResource> res = (id<MTLResource>)res_handle;
[enc useResource:res usage:(MTLResourceUsage)usage stages:MTLRenderStageVertex|MTLRenderStageFragment];
}
}
void
mtl_render_use_resources(mtl_render_encoder *encoder,
mtl_resource **resource_handles, uint32_t count,
enum mtl_resource_usage usage)
{
@autoreleasepool {
// id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
id<MTLResource> *handles = (id<MTLResource>*)resource_handles;
for (uint32_t i = 0u; i < count; ++i) {
if (handles[i] != NULL)
mtl_render_use_resource(encoder, handles[i], usage);
}
/* TODO_KOSMICKRISP No null values in the array or Metal complains */
// [enc useResources:handles count:count usage:(MTLResourceUsage)usage];
}
}
void
mtl_render_use_heaps(mtl_render_encoder *encoder, mtl_heap **heaps,
uint32_t count)
{
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (id<MTLRenderCommandEncoder>)encoder;
id<MTLHeap> *handles = (id<MTLHeap>*)heaps;
[enc useHeaps:handles count:count stages:MTLRenderStageVertex|MTLRenderStageFragment];
}
}
+205
View File
@@ -0,0 +1,205 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef MTL_FORMAT_H
#define MTL_FORMAT_H 1
/* TODO_KOSMICKRISP Haven't modified all
* Slightly modified naming so they match to enum pipe_format for convenience
*/
enum mtl_pixel_format {
MTL_PIXEL_FORMAT_INVALID = 0,
/* Normal 8 bit formats */
MTL_PIXEL_FORMAT_A8_UNORM = 1,
MTL_PIXEL_FORMAT_R8_UNORM = 10,
MTL_PIXEL_FORMAT_R8_SRGB = 11,
MTL_PIXEL_FORMAT_R8_SNORM = 12,
MTL_PIXEL_FORMAT_R8_UINT = 13,
MTL_PIXEL_FORMAT_R8_SINT = 14,
/* Normal 16 bit formats */
MTL_PIXEL_FORMAT_R16_UNORM = 20,
MTL_PIXEL_FORMAT_R16_SNORM = 22,
MTL_PIXEL_FORMAT_R16_UINT = 23,
MTL_PIXEL_FORMAT_R16_SINT = 24,
MTL_PIXEL_FORMAT_R16_FLOAT = 25,
MTL_PIXEL_FORMAT_R8G8_UNORM = 30,
MTL_PIXEL_FORMAT_R8G8_SRGB = 31,
MTL_PIXEL_FORMAT_R8G8_SNORM = 32,
MTL_PIXEL_FORMAT_R8G8_UINT = 33,
MTL_PIXEL_FORMAT_R8G8_SINT = 34,
/* Packed 16 bit formats */
MTL_PIXEL_FORMAT_B5G6R5_UNORM = 40,
MTL_PIXEL_FORMAT_A1B5G5R5_UNORM = 41,
MTL_PIXEL_FORMAT_A4B4G4R4_UNORM = 42,
MTL_PIXEL_FORMAT_B5G5R5A1_UNORM = 43,
/* Normal 32 bit formats */
MTL_PIXEL_FORMAT_R32_UINT = 53,
MTL_PIXEL_FORMAT_R32_SINT = 54,
MTL_PIXEL_FORMAT_R32_FLOAT = 55,
MTL_PIXEL_FORMAT_R16G16_UNORM = 60,
MTL_PIXEL_FORMAT_R16G16_SNORM = 62,
MTL_PIXEL_FORMAT_R16G16_UINT = 63,
MTL_PIXEL_FORMAT_R16G16_SINT = 64,
MTL_PIXEL_FORMAT_R16G16_FLOAT = 65,
MTL_PIXEL_FORMAT_R8G8B8A8_UNORM = 70,
MTL_PIXEL_FORMAT_R8G8B8A8_SRGB = 71,
MTL_PIXEL_FORMAT_R8G8B8A8_SNORM = 72,
MTL_PIXEL_FORMAT_R8G8B8A8_UINT = 73,
MTL_PIXEL_FORMAT_R8G8B8A8_SINT = 74,
MTL_PIXEL_FORMAT_B8G8R8A8_UNORM = 80,
MTL_PIXEL_FORMAT_B8G8R8A8_SRGB = 81,
/* Packed 32 bit formats */
MTL_PIXEL_FORMAT_R10G10B10A2_UNORM = 90,
MTL_PIXEL_FORMAT_R10G10B10A2_UINT = 91,
MTL_PIXEL_FORMAT_R11G11B10_FLOAT = 92,
MTL_PIXEL_FORMAT_R9G9B9E5_FLOAT = 93,
MTL_PIXEL_FORMAT_B10G10R10A2_UNORM = 94,
MTL_PIXEL_FORMAT_BGR10_XR = 554,
MTL_PIXEL_FORMAT_BGR10_XR_SRGB = 555,
/* Normal 64 bit formats */
MTL_PIXEL_FORMAT_R32G32_UINT = 103,
MTL_PIXEL_FORMAT_R32G32_SINT = 104,
MTL_PIXEL_FORMAT_R32G32_FLOAT = 105,
MTL_PIXEL_FORMAT_R16G16B16A16_UNORM = 110,
MTL_PIXEL_FORMAT_R16G16B16A16_SNORM = 112,
MTL_PIXEL_FORMAT_R16G16B16A16_UINT = 113,
MTL_PIXEL_FORMAT_R16G16B16A16_SINT = 114,
MTL_PIXEL_FORMAT_R16G16B16A16_FLOAT = 115,
MTL_PIXEL_FORMAT_BGRA10_XR = 552,
MTL_PIXEL_FORMAT_BGRA10_XR_SRGB = 553,
/* Normal 128 bit formats */
MTL_PIXEL_FORMAT_R32G32B32A32_UINT = 123,
MTL_PIXEL_FORMAT_R32G32B32A32_SINT = 124,
MTL_PIXEL_FORMAT_R32G32B32A32_FLOAT = 125,
/* Compressed formats. */
/* S3TC/DXT */
MTL_PIXEL_FORMAT_DXT1_RGBA = 130,
MTL_PIXEL_FORMAT_DXT1_SRGBA = 131,
MTL_PIXEL_FORMAT_DXT3_RGBA = 132,
MTL_PIXEL_FORMAT_DXT3_SRGBA = 133,
MTL_PIXEL_FORMAT_DXT5_RGBA = 134,
MTL_PIXEL_FORMAT_DXT5_SRGBA = 135,
/* RGTC */
MTL_PIXEL_FORMAT_RGTC1_UNORM = 140,
MTL_PIXEL_FORMAT_RGTC1_SNORM = 141,
MTL_PIXEL_FORMAT_RGTC2_UNORM = 142,
MTL_PIXEL_FORMAT_RGTC2_SNORM = 143,
/* BPTC */
MTL_PIXEL_FORMAT_BPTC_RGB_FLOAT = 150,
MTL_PIXEL_FORMAT_BPTC_RGB_UFLOAT = 151,
MTL_PIXEL_FORMAT_BPTC_RGBA_UNORM = 152,
MTL_PIXEL_FORMAT_BPTC_SRGBA = 153,
/* PVRTC */
MTL_PIXEL_FORMAT_PVRTC_RGB_2BPP = 160,
MTL_PIXEL_FORMAT_PVRTC_RGB_2BPP_SRGB = 161,
MTL_PIXEL_FORMAT_PVRTC_RGB_4BPP = 162,
MTL_PIXEL_FORMAT_PVRTC_RGB_4BPP_SRGB = 163,
MTL_PIXEL_FORMAT_PVRTC_RGBA_2BPP = 164,
MTL_PIXEL_FORMAT_PVRTC_RGBA_2BPP_SRGB = 165,
MTL_PIXEL_FORMAT_PVRTC_RGBA_4BPP = 166,
MTL_PIXEL_FORMAT_PVRTC_RGBA_4BPP_SRGB = 167,
/* ETC2 */
MTL_PIXEL_FORMAT_ETC2_R11_UNORM = 170,
MTL_PIXEL_FORMAT_ETC2_R11_SNORM = 172,
MTL_PIXEL_FORMAT_ETC2_RG11_UNORM = 174,
MTL_PIXEL_FORMAT_ETC2_RG11_SNORM = 176,
MTL_PIXEL_FORMAT_ETC2_RGBA8 = 178,
MTL_PIXEL_FORMAT_ETC2_SRGBA8 = 179,
MTL_PIXEL_FORMAT_ETC2_RGB8 = 180,
MTL_PIXEL_FORMAT_ETC2_SRGB8 = 181,
MTL_PIXEL_FORMAT_ETC2_RGB8A1 = 182,
MTL_PIXEL_FORMAT_ETC2_SRGB8A1 = 183,
/* ASTC */
MTL_PIXEL_FORMAT_ASTC_4x4_SRGB = 186,
MTL_PIXEL_FORMAT_ASTC_5x4_SRGB = 187,
MTL_PIXEL_FORMAT_ASTC_5x5_SRGB = 188,
MTL_PIXEL_FORMAT_ASTC_6x5_SRGB = 189,
MTL_PIXEL_FORMAT_ASTC_6x6_SRGB = 190,
MTL_PIXEL_FORMAT_ASTC_8x5_SRGB = 192,
MTL_PIXEL_FORMAT_ASTC_8x6_SRGB = 193,
MTL_PIXEL_FORMAT_ASTC_8x8_SRGB = 194,
MTL_PIXEL_FORMAT_ASTC_10x5_SRGB = 195,
MTL_PIXEL_FORMAT_ASTC_10x6_SRGB = 196,
MTL_PIXEL_FORMAT_ASTC_10x8_SRGB = 197,
MTL_PIXEL_FORMAT_ASTC_10x10_SRGB = 198,
MTL_PIXEL_FORMAT_ASTC_12x10_SRGB = 199,
MTL_PIXEL_FORMAT_ASTC_12x12_SRGB = 200,
MTL_PIXEL_FORMAT_ASTC_4x4 = 204,
MTL_PIXEL_FORMAT_ASTC_5x4 = 205,
MTL_PIXEL_FORMAT_ASTC_5x5 = 206,
MTL_PIXEL_FORMAT_ASTC_6x5 = 207,
MTL_PIXEL_FORMAT_ASTC_6x6 = 208,
MTL_PIXEL_FORMAT_ASTC_8x5 = 210,
MTL_PIXEL_FORMAT_ASTC_8x6 = 211,
MTL_PIXEL_FORMAT_ASTC_8x8 = 212,
MTL_PIXEL_FORMAT_ASTC_10x5 = 213,
MTL_PIXEL_FORMAT_ASTC_10x6 = 214,
MTL_PIXEL_FORMAT_ASTC_10x8 = 215,
MTL_PIXEL_FORMAT_ASTC_10x10 = 216,
MTL_PIXEL_FORMAT_ASTC_12x10 = 217,
MTL_PIXEL_FORMAT_ASTC_12x12 = 218,
/* ASTC HDR (High Dynamic Range) */
MTL_PIXEL_FORMAT_ASTC_4x4_FLOAT = 222,
MTL_PIXEL_FORMAT_ASTC_5x4_FLOAT = 223,
MTL_PIXEL_FORMAT_ASTC_5x5_FLOAT = 224,
MTL_PIXEL_FORMAT_ASTC_6x5_FLOAT = 225,
MTL_PIXEL_FORMAT_ASTC_6x6_FLOAT = 226,
MTL_PIXEL_FORMAT_ASTC_8x5_FLOAT = 228,
MTL_PIXEL_FORMAT_ASTC_8x6_FLOAT = 229,
MTL_PIXEL_FORMAT_ASTC_8x8_FLOAT = 230,
MTL_PIXEL_FORMAT_ASTC_10x5_FLOAT = 231,
MTL_PIXEL_FORMAT_ASTC_10x6_FLOAT = 232,
MTL_PIXEL_FORMAT_ASTC_10x8_FLOAT = 233,
MTL_PIXEL_FORMAT_ASTC_10x10_FLOAT = 234,
MTL_PIXEL_FORMAT_ASTC_12x10_FLOAT = 235,
MTL_PIXEL_FORMAT_ASTC_12x12_FLOAT = 236,
/* YUV */
MTL_PIXEL_FORMAT_GBGR422 = 240,
MTL_PIXEL_FORMAT_BGRG422 = 241,
/* DEPTH */
MTL_PIXEL_FORMAT_Z16_UNORM = 250,
MTL_PIXEL_FORMAT_Z32_FLOAT = 252,
/* STENCIL */
MTL_PIXEL_FORMAT_S8_UINT = 253,
/* DEPTH STENCIL */
MTL_PIXEL_FORMAT_Z24_UNORM_S8_UINT = 255,
MTL_PIXEL_FORMAT_Z32_FLOAT_S8X24_UINT = 260,
MTL_PIXEL_FORMAT_X32_S8X24_UINT = 261,
MTL_PIXEL_FORMAT_X24_S8_UINT = 262,
};
#endif /* MTL_FORMAT_H */
+30
View File
@@ -0,0 +1,30 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef MTL_HEAP_H
#define MTL_HEAP_H 1
#include "mtl_types.h"
#include <inttypes.h>
/* TODO_KOSMICKRISP We should move this struct to the bridge side. */
struct kk_image_layout;
/* Creation */
mtl_heap *mtl_new_heap(mtl_device *device, uint64_t size,
enum mtl_resource_options resource_options);
/* Utils */
uint64_t mtl_heap_get_size(mtl_heap *heap);
/* Allocation from heap */
mtl_buffer *mtl_new_buffer_with_length(mtl_heap *heap, uint64_t size_B,
uint64_t offset_B);
mtl_texture *mtl_new_texture_with_descriptor(
mtl_heap *heap, const struct kk_image_layout *layout, uint64_t offset);
#endif /* MTL_HEAP_H */
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_heap.h"
/* TODO_KOSMICKRISP Remove */
#include "kk_private.h"
#include "kk_image_layout.h"
#include <Metal/MTLHeap.h>
/* Creation */
mtl_heap *
mtl_new_heap(mtl_device *device, uint64_t size,
enum mtl_resource_options resource_options)
{
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
MTLHeapDescriptor *descriptor = [[MTLHeapDescriptor new] autorelease];
descriptor.type = MTLHeapTypePlacement;
descriptor.resourceOptions = (MTLResourceOptions)resource_options;
descriptor.size = size;
descriptor.sparsePageSize = MTLSparsePageSize16;
return [dev newHeapWithDescriptor:descriptor];
}
}
/* Utils */
uint64_t
mtl_heap_get_size(mtl_heap *heap)
{
@autoreleasepool {
id<MTLHeap> hp = (id<MTLHeap>)heap;
return hp.size;
}
}
static MTLTextureDescriptor *
mtl_new_texture_descriptor(const struct kk_image_layout *layout)
{
@autoreleasepool {
MTLTextureDescriptor *descriptor = [MTLTextureDescriptor new];
descriptor.textureType = (MTLTextureType)layout->type;
descriptor.pixelFormat = layout->format.mtl;
descriptor.width = layout->width_px;
descriptor.height = layout->height_px;
descriptor.depth = layout->depth_px;
descriptor.mipmapLevelCount = layout->levels;
descriptor.sampleCount = layout->sample_count_sa;
descriptor.arrayLength = layout->layers;
descriptor.allowGPUOptimizedContents = layout->optimized_layout;
descriptor.usage = (MTLTextureUsage)layout->usage;
/* We don't set the swizzle because Metal complains when the usage has store or render target with swizzle... */
return descriptor;
}
}
/* Allocation from heap */
mtl_buffer *
mtl_new_buffer_with_length(mtl_heap *heap, uint64_t size_B, uint64_t offset_B)
{
@autoreleasepool {
id<MTLHeap> hp = (id<MTLHeap>)heap;
return (mtl_buffer *)[hp newBufferWithLength:size_B options:KK_MTL_RESOURCE_OPTIONS offset:offset_B];
}
}
mtl_texture *
mtl_new_texture_with_descriptor(mtl_heap *heap,
const struct kk_image_layout *layout,
uint64_t offset)
{
@autoreleasepool {
id<MTLHeap> hp = (id<MTLHeap>)heap;
MTLTextureDescriptor *descriptor = [mtl_new_texture_descriptor(layout) autorelease];
descriptor.resourceOptions = hp.resourceOptions;
return [hp newTextureWithDescriptor:descriptor offset:offset];
}
}
+16
View File
@@ -0,0 +1,16 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef MTL_LIBRARY_H
#define MTL_LIBRARY_H 1
#include "mtl_types.h"
mtl_library *mtl_new_library(mtl_device *device, const char *src);
mtl_function *mtl_new_function_with_name(mtl_library *lib,
const char *entry_point);
#endif /* MTL_LIBRARY_H */
+43
View File
@@ -0,0 +1,43 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_library.h"
#include <Metal/MTLDevice.h>
mtl_library *
mtl_new_library(mtl_device *device, const char *src)
{
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
id<MTLLibrary> lib = NULL;
NSString *nsstr = [NSString stringWithCString:src encoding:NSASCIIStringEncoding];
NSError *error;
MTLCompileOptions *comp_opts = [MTLCompileOptions new];
comp_opts.languageVersion = MTLLanguageVersion3_2;
comp_opts.mathMode = MTLMathModeSafe;
comp_opts.mathFloatingPointFunctions = MTLMathFloatingPointFunctionsPrecise;
lib = [dev newLibraryWithSource:nsstr options:comp_opts error:&error];
if (error != nil) {
fprintf(stderr, "Failed to create MTLLibrary: %s\n", [error.localizedDescription UTF8String]);
}
[comp_opts release];
return lib;
}
}
mtl_function *
mtl_new_function_with_name(mtl_library *lib, const char *entry_point)
{
@autoreleasepool {
id<MTLLibrary> mtl_lib = (id<MTLLibrary>)lib;
NSString *ns_entry_point = [NSString stringWithCString:entry_point encoding:NSASCIIStringEncoding];
return [mtl_lib newFunctionWithName:ns_entry_point];
}
}
+169
View File
@@ -0,0 +1,169 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef MTL_RENDER_STATE_H
#define MTL_RENDER_STATE_H 1
#include "mtl_types.h"
#include <inttypes.h>
#include <stdbool.h>
/* Bridge enums */
enum mtl_pixel_format;
/* TODO_KOSMICKRISP Remove */
enum VkCompareOp;
enum VkStencilOp;
/* Render pass descriptor */
mtl_render_pass_descriptor *mtl_new_render_pass_descriptor(void);
mtl_render_pass_attachment_descriptor *
mtl_render_pass_descriptor_get_color_attachment(
mtl_render_pass_descriptor *descriptor, uint32_t index);
mtl_render_pass_attachment_descriptor *
mtl_render_pass_descriptor_get_depth_attachment(
mtl_render_pass_descriptor *descriptor);
mtl_render_pass_attachment_descriptor *
mtl_render_pass_descriptor_get_stencil_attachment(
mtl_render_pass_descriptor *descriptor);
void mtl_render_pass_attachment_descriptor_set_texture(
mtl_render_pass_attachment_descriptor *descriptor, mtl_texture *texture);
void mtl_render_pass_attachment_descriptor_set_level(
mtl_render_pass_attachment_descriptor *descriptor, uint32_t level);
void mtl_render_pass_attachment_descriptor_set_slice(
mtl_render_pass_attachment_descriptor *descriptor, uint32_t slice);
void mtl_render_pass_attachment_descriptor_set_load_action(
mtl_render_pass_attachment_descriptor *descriptor,
enum mtl_load_action action);
void mtl_render_pass_attachment_descriptor_set_store_action(
mtl_render_pass_attachment_descriptor *descriptor,
enum mtl_store_action action);
void mtl_render_pass_attachment_descriptor_set_clear_color(
mtl_render_pass_attachment_descriptor *descriptor,
struct mtl_clear_color clear_color);
void mtl_render_pass_attachment_descriptor_set_clear_depth(
mtl_render_pass_attachment_descriptor *descriptor, double depth);
void mtl_render_pass_attachment_descriptor_set_clear_stencil(
mtl_render_pass_attachment_descriptor *descriptor, uint32_t stencil);
void mtl_render_pass_descriptor_set_render_target_array_length(
mtl_render_pass_descriptor *descriptor, uint32_t length);
void mtl_render_pass_descriptor_set_render_target_width(
mtl_render_pass_descriptor *descriptor, uint32_t width);
void mtl_render_pass_descriptor_set_render_target_height(
mtl_render_pass_descriptor *descriptor, uint32_t height);
void mtl_render_pass_descriptor_set_default_raster_sample_count(
mtl_render_pass_descriptor *descriptor, uint32_t sample_count);
void mtl_render_pass_descriptor_set_sample_positions(
mtl_render_pass_descriptor *descriptor,
const struct mtl_sample_position *positions, uint32_t count);
void mtl_render_pass_descriptor_set_visibility_buffer(
mtl_render_pass_descriptor *descriptor, mtl_buffer *visibility_buffer);
/* Render pipeline descriptor */
mtl_render_pipeline_descriptor *mtl_new_render_pipeline_descriptor(void);
void mtl_render_pipeline_descriptor_set_vertex_shader(
mtl_render_pass_descriptor *descriptor, mtl_function *shader);
void mtl_render_pipeline_descriptor_set_fragment_shader(
mtl_render_pass_descriptor *descriptor, mtl_function *shader);
void mtl_render_pipeline_descriptor_set_input_primitive_topology(
mtl_render_pass_descriptor *descriptor,
enum mtl_primitive_topology_class topology_class);
void mtl_render_pipeline_descriptor_set_color_attachment_format(
mtl_render_pass_descriptor *descriptor, uint8_t index,
enum mtl_pixel_format format);
void mtl_render_pipeline_descriptor_set_depth_attachment_format(
mtl_render_pass_descriptor *descriptor, enum mtl_pixel_format format);
void mtl_render_pipeline_descriptor_set_stencil_attachment_format(
mtl_render_pass_descriptor *descriptor, enum mtl_pixel_format format);
void mtl_render_pipeline_descriptor_set_raster_sample_count(
mtl_render_pass_descriptor *descriptor, uint32_t sample_count);
void mtl_render_pipeline_descriptor_set_alpha_to_coverage(
mtl_render_pass_descriptor *descriptor, bool enabled);
void mtl_render_pipeline_descriptor_set_alpha_to_one(
mtl_render_pass_descriptor *descriptor, bool enabled);
void mtl_render_pipeline_descriptor_set_rasterization_enabled(
mtl_render_pass_descriptor *descriptor, bool enabled);
void mtl_render_pipeline_descriptor_set_max_vertex_amplification_count(
mtl_render_pass_descriptor *descriptor, uint32_t count);
/* Render pipeline */
mtl_render_pipeline_state *
mtl_new_render_pipeline(mtl_device *device,
mtl_render_pass_descriptor *descriptor);
/* Stencil descriptor */
mtl_stencil_descriptor *mtl_new_stencil_descriptor(void);
void mtl_stencil_descriptor_set_stencil_failure_operation(
mtl_stencil_descriptor *descriptor, enum VkStencilOp op);
void mtl_stencil_descriptor_set_depth_failure_operation(
mtl_stencil_descriptor *descriptor, enum VkStencilOp op);
void mtl_stencil_descriptor_set_depth_stencil_pass_operation(
mtl_stencil_descriptor *descriptor, enum VkStencilOp op);
void mtl_stencil_descriptor_set_stencil_compare_function(
mtl_stencil_descriptor *descriptor, enum VkCompareOp op);
void mtl_stencil_descriptor_set_read_mask(mtl_stencil_descriptor *descriptor,
uint32_t mask);
void mtl_stencil_descriptor_set_write_mask(mtl_stencil_descriptor *descriptor,
uint32_t mask);
/* Depth stencil descriptor */
mtl_depth_stencil_descriptor *mtl_new_depth_stencil_descriptor(void);
void mtl_depth_stencil_descriptor_set_depth_compare_function(
mtl_depth_stencil_descriptor *descriptor, enum VkCompareOp op);
void mtl_depth_stencil_descriptor_set_depth_write_enabled(
mtl_depth_stencil_descriptor *descriptor, bool enable_write);
void mtl_depth_stencil_descriptor_set_back_face_stencil(
mtl_depth_stencil_descriptor *descriptor,
mtl_stencil_descriptor *stencil_descriptor);
void mtl_depth_stencil_descriptor_set_front_face_stencil(
mtl_depth_stencil_descriptor *descriptor,
mtl_stencil_descriptor *stencil_descriptor);
/* Depth stencil state */
mtl_depth_stencil_state *
mtl_new_depth_stencil_state(mtl_device *device,
mtl_depth_stencil_descriptor *descriptor);
#endif /* MTL_RENDER_STATE_H */
+495
View File
@@ -0,0 +1,495 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_render_state.h"
#include "mtl_format.h"
/* TODO_KOSMICKRISP Remove */
#include "vk_to_mtl_map.h"
/* TODO_KOSMICKRISP Remove */
#include "vulkan/vulkan.h"
#include <Metal/MTLRenderPass.h>
#include <Metal/MTLRenderPipeline.h>
#include <Metal/MTLDepthStencil.h>
/* Render pass descriptor */
mtl_render_pass_descriptor *
mtl_new_render_pass_descriptor(void)
{
@autoreleasepool {
return [[MTLRenderPassDescriptor renderPassDescriptor] retain];
}
}
mtl_render_pass_attachment_descriptor *
mtl_render_pass_descriptor_get_color_attachment(
mtl_render_pass_descriptor *descriptor, uint32_t index)
{
@autoreleasepool {
MTLRenderPassDescriptor *desc = (MTLRenderPassDescriptor *)descriptor;
return desc.colorAttachments[index];
}
}
mtl_render_pass_attachment_descriptor *
mtl_render_pass_descriptor_get_depth_attachment(
mtl_render_pass_descriptor *descriptor)
{
@autoreleasepool {
MTLRenderPassDescriptor *desc = (MTLRenderPassDescriptor *)descriptor;
return desc.depthAttachment;
}
}
mtl_render_pass_attachment_descriptor *
mtl_render_pass_descriptor_get_stencil_attachment(
mtl_render_pass_descriptor *descriptor)
{
@autoreleasepool {
MTLRenderPassDescriptor *desc = (MTLRenderPassDescriptor *)descriptor;
return desc.stencilAttachment;
}
}
void
mtl_render_pass_attachment_descriptor_set_texture(
mtl_render_pass_attachment_descriptor *descriptor, mtl_texture *texture)
{
@autoreleasepool {
MTLRenderPassAttachmentDescriptor *desc = (MTLRenderPassAttachmentDescriptor *)descriptor;
desc.texture = (id<MTLTexture>)texture;
}
}
void
mtl_render_pass_attachment_descriptor_set_level(
mtl_render_pass_attachment_descriptor *descriptor, uint32_t level)
{
@autoreleasepool {
MTLRenderPassAttachmentDescriptor *desc = (MTLRenderPassAttachmentDescriptor *)descriptor;
desc.level = level;
}
}
void
mtl_render_pass_attachment_descriptor_set_slice(
mtl_render_pass_attachment_descriptor *descriptor, uint32_t slice)
{
@autoreleasepool {
MTLRenderPassAttachmentDescriptor *desc = (MTLRenderPassAttachmentDescriptor *)descriptor;
desc.slice = slice;
}
}
void
mtl_render_pass_attachment_descriptor_set_load_action(
mtl_render_pass_attachment_descriptor *descriptor,
enum mtl_load_action action)
{
@autoreleasepool {
MTLRenderPassAttachmentDescriptor *desc = (MTLRenderPassAttachmentDescriptor *)descriptor;
desc.loadAction = (MTLLoadAction)action;
}
}
void
mtl_render_pass_attachment_descriptor_set_store_action(
mtl_render_pass_attachment_descriptor *descriptor,
enum mtl_store_action action)
{
@autoreleasepool {
MTLRenderPassAttachmentDescriptor *desc = (MTLRenderPassAttachmentDescriptor *)descriptor;
desc.storeAction = (MTLStoreAction)action;
desc.storeActionOptions = MTLStoreActionOptionNone; /* TODO_KOSMICKRISP Maybe expose this? */
}
}
void
mtl_render_pass_attachment_descriptor_set_clear_color(
mtl_render_pass_attachment_descriptor *descriptor,
struct mtl_clear_color clear_color)
{
@autoreleasepool {
MTLRenderPassColorAttachmentDescriptor *desc = (MTLRenderPassColorAttachmentDescriptor *)descriptor;
desc.clearColor = MTLClearColorMake(clear_color.red, clear_color.green, clear_color.blue, clear_color.alpha);
}
}
void
mtl_render_pass_attachment_descriptor_set_clear_depth(
mtl_render_pass_attachment_descriptor *descriptor, double depth)
{
@autoreleasepool {
MTLRenderPassDepthAttachmentDescriptor *desc = (MTLRenderPassDepthAttachmentDescriptor *)descriptor;
desc.clearDepth = depth;
}
}
void
mtl_render_pass_attachment_descriptor_set_clear_stencil(mtl_render_pass_attachment_descriptor *descriptor,
uint32_t stencil)
{
@autoreleasepool {
MTLRenderPassStencilAttachmentDescriptor *desc = (MTLRenderPassStencilAttachmentDescriptor *)descriptor;
desc.clearStencil = stencil;
}
}
void
mtl_render_pass_descriptor_set_render_target_array_length(mtl_render_pass_descriptor *descriptor,
uint32_t length)
{
@autoreleasepool {
MTLRenderPassDescriptor *desc = (MTLRenderPassDescriptor *)descriptor;
desc.renderTargetArrayLength = length;
}
}
void
mtl_render_pass_descriptor_set_render_target_width(mtl_render_pass_descriptor *descriptor,
uint32_t width)
{
@autoreleasepool {
MTLRenderPassDescriptor *desc = (MTLRenderPassDescriptor *)descriptor;
desc.renderTargetWidth = width;
}
}
void
mtl_render_pass_descriptor_set_render_target_height(mtl_render_pass_descriptor *descriptor,
uint32_t height)
{
@autoreleasepool {
MTLRenderPassDescriptor *desc = (MTLRenderPassDescriptor *)descriptor;
desc.renderTargetHeight = height;
}
}
void
mtl_render_pass_descriptor_set_default_raster_sample_count(mtl_render_pass_descriptor *descriptor,
uint32_t sample_count)
{
@autoreleasepool {
MTLRenderPassDescriptor *desc = (MTLRenderPassDescriptor *)descriptor;
desc.defaultRasterSampleCount = sample_count;
}
}
void
mtl_render_pass_descriptor_set_sample_positions(
mtl_render_pass_descriptor *descriptor,
const struct mtl_sample_position *positions,
uint32_t count)
{
@autoreleasepool {
MTLRenderPassDescriptor *desc = (MTLRenderPassDescriptor *)descriptor;
if (count > 0) {
MTLSamplePosition pos[count];
for (uint32_t i = 0; i < count; i++) {
pos[i] = MTLSamplePositionMake(positions[i].x, positions[i].y);
}
[desc setSamplePositions:pos count:count];
} else {
[desc setSamplePositions:NULL count:count];
}
}
}
void
mtl_render_pass_descriptor_set_visibility_buffer(mtl_render_pass_descriptor *descriptor,
mtl_buffer *visibility_buffer)
{
@autoreleasepool {
MTLRenderPassDescriptor *desc = (MTLRenderPassDescriptor *)descriptor;
id<MTLBuffer> buffer = (id<MTLBuffer>)visibility_buffer;
desc.visibilityResultBuffer = buffer;
}
}
/* Render pipeline descriptor */
mtl_render_pipeline_descriptor *
mtl_new_render_pipeline_descriptor()
{
@autoreleasepool {
return [[MTLRenderPipelineDescriptor alloc] init];
}
}
void
mtl_render_pipeline_descriptor_set_vertex_shader(mtl_render_pass_descriptor *descriptor,
mtl_function *shader)
{
@autoreleasepool {
MTLRenderPipelineDescriptor *desc = (MTLRenderPipelineDescriptor *)descriptor;
desc.vertexFunction = (id<MTLFunction>)shader;
}
}
void
mtl_render_pipeline_descriptor_set_fragment_shader(mtl_render_pass_descriptor *descriptor,
mtl_function *shader)
{
@autoreleasepool {
MTLRenderPipelineDescriptor *desc = (MTLRenderPipelineDescriptor *)descriptor;
desc.fragmentFunction = (id<MTLFunction>)shader;
}
}
void
mtl_render_pipeline_descriptor_set_input_primitive_topology(mtl_render_pass_descriptor *descriptor,
enum mtl_primitive_topology_class class)
{
@autoreleasepool {
MTLRenderPipelineDescriptor *desc = (MTLRenderPipelineDescriptor *)descriptor;
desc.inputPrimitiveTopology = (MTLPrimitiveTopologyClass)class;
}
}
void
mtl_render_pipeline_descriptor_set_color_attachment_format(mtl_render_pass_descriptor *descriptor,
uint8_t index,
enum mtl_pixel_format format)
{
@autoreleasepool {
MTLRenderPipelineDescriptor *desc = (MTLRenderPipelineDescriptor *)descriptor;
desc.colorAttachments[index].pixelFormat = (MTLPixelFormat)format;
}
}
void
mtl_render_pipeline_descriptor_set_depth_attachment_format(mtl_render_pass_descriptor *descriptor,
enum mtl_pixel_format format)
{
@autoreleasepool {
MTLRenderPipelineDescriptor *desc = (MTLRenderPipelineDescriptor *)descriptor;
desc.depthAttachmentPixelFormat = (MTLPixelFormat)format;
}
}
void
mtl_render_pipeline_descriptor_set_stencil_attachment_format(mtl_render_pass_descriptor *descriptor,
enum mtl_pixel_format format)
{
@autoreleasepool {
MTLRenderPipelineDescriptor *desc = (MTLRenderPipelineDescriptor *)descriptor;
desc.stencilAttachmentPixelFormat = (MTLPixelFormat)format;
}
}
void
mtl_render_pipeline_descriptor_set_raster_sample_count(mtl_render_pass_descriptor *descriptor,
uint32_t sample_count)
{
@autoreleasepool {
MTLRenderPipelineDescriptor *desc = (MTLRenderPipelineDescriptor *)descriptor;
desc.rasterSampleCount = sample_count;
}
}
void
mtl_render_pipeline_descriptor_set_alpha_to_coverage(mtl_render_pass_descriptor *descriptor,
bool enabled)
{
@autoreleasepool {
MTLRenderPipelineDescriptor *desc = (MTLRenderPipelineDescriptor *)descriptor;
desc.alphaToCoverageEnabled = enabled;
}
}
void
mtl_render_pipeline_descriptor_set_alpha_to_one(mtl_render_pass_descriptor *descriptor,
bool enabled)
{
@autoreleasepool {
MTLRenderPipelineDescriptor *desc = (MTLRenderPipelineDescriptor *)descriptor;
desc.alphaToOneEnabled = enabled;
}
}
void
mtl_render_pipeline_descriptor_set_rasterization_enabled(mtl_render_pass_descriptor *descriptor,
bool enabled)
{
@autoreleasepool {
MTLRenderPipelineDescriptor *desc = (MTLRenderPipelineDescriptor *)descriptor;
desc.rasterizationEnabled = enabled;
}
}
void
mtl_render_pipeline_descriptor_set_max_vertex_amplification_count( mtl_render_pass_descriptor *descriptor,
uint32_t count)
{
@autoreleasepool {
MTLRenderPipelineDescriptor *desc = (MTLRenderPipelineDescriptor *)descriptor;
desc.maxVertexAmplificationCount = count;
}
}
/* Render pipeline */
mtl_render_pipeline_state *
mtl_new_render_pipeline(mtl_device *device, mtl_render_pass_descriptor *descriptor)
{
@autoreleasepool {
MTLRenderPipelineDescriptor *desc = (MTLRenderPipelineDescriptor *)descriptor;
id<MTLDevice> dev = (id<MTLDevice>)device;
NSError *error = nil;
mtl_render_pipeline_state *pipeline = [dev newRenderPipelineStateWithDescriptor:desc error:&error];
if (error != nil) {
fprintf(stderr, "Failed to create MTLLibrary: %s\n", [error.localizedDescription UTF8String]);
}
return pipeline;
}
}
/* Stencil descriptor */
mtl_stencil_descriptor *
mtl_new_stencil_descriptor()
{
@autoreleasepool {
return [[MTLStencilDescriptor new] init];
}
}
/* TODO_KOSMICKRISP Move this to map */
static MTLStencilOperation
map_vk_stencil_op_to_mtl_stencil_operation(VkStencilOp op)
{
switch (op) {
case VK_STENCIL_OP_KEEP:
return MTLStencilOperationKeep;
case VK_STENCIL_OP_ZERO:
return MTLStencilOperationZero;
case VK_STENCIL_OP_REPLACE:
return MTLStencilOperationReplace;
case VK_STENCIL_OP_INCREMENT_AND_CLAMP:
return MTLStencilOperationIncrementClamp;
case VK_STENCIL_OP_DECREMENT_AND_CLAMP:
return MTLStencilOperationDecrementClamp;
case VK_STENCIL_OP_INVERT:
return MTLStencilOperationInvert;
case VK_STENCIL_OP_INCREMENT_AND_WRAP:
return MTLStencilOperationIncrementWrap;
case VK_STENCIL_OP_DECREMENT_AND_WRAP:
return MTLStencilOperationDecrementWrap;
default:
assert(false && "Unsupported VkStencilOp");
return MTLStencilOperationZero;
};
}
void
mtl_stencil_descriptor_set_stencil_failure_operation(mtl_stencil_descriptor *descriptor, VkStencilOp op)
{
@autoreleasepool {
MTLStencilDescriptor *desc = (MTLStencilDescriptor *)descriptor;
desc.stencilFailureOperation = map_vk_stencil_op_to_mtl_stencil_operation(op);
}
}
void
mtl_stencil_descriptor_set_depth_failure_operation(mtl_stencil_descriptor *descriptor, VkStencilOp op)
{
@autoreleasepool {
MTLStencilDescriptor *desc = (MTLStencilDescriptor *)descriptor;
desc.depthFailureOperation = map_vk_stencil_op_to_mtl_stencil_operation(op);
}
}
void
mtl_stencil_descriptor_set_depth_stencil_pass_operation(mtl_stencil_descriptor *descriptor, VkStencilOp op)
{
@autoreleasepool {
MTLStencilDescriptor *desc = (MTLStencilDescriptor *)descriptor;
desc.depthStencilPassOperation = map_vk_stencil_op_to_mtl_stencil_operation(op);
}
}
void
mtl_stencil_descriptor_set_stencil_compare_function(mtl_stencil_descriptor *descriptor, VkCompareOp op)
{
@autoreleasepool {
MTLStencilDescriptor *desc = (MTLStencilDescriptor *)descriptor;
desc.stencilCompareFunction = (MTLCompareFunction)vk_compare_op_to_mtl_compare_function(op);
}
}
void
mtl_stencil_descriptor_set_read_mask(mtl_stencil_descriptor *descriptor, uint32_t mask)
{
@autoreleasepool {
MTLStencilDescriptor *desc = (MTLStencilDescriptor *)descriptor;
desc.readMask = mask;
}
}
void
mtl_stencil_descriptor_set_write_mask(mtl_stencil_descriptor *descriptor, uint32_t mask)
{
@autoreleasepool {
MTLStencilDescriptor *desc = (MTLStencilDescriptor *)descriptor;
desc.writeMask = mask;
}
}
/* Depth stencil descriptor */
mtl_depth_stencil_descriptor *
mtl_new_depth_stencil_descriptor()
{
@autoreleasepool {
return [[MTLDepthStencilDescriptor new] init];
}
}
void
mtl_depth_stencil_descriptor_set_depth_compare_function(mtl_depth_stencil_descriptor *descriptor, VkCompareOp op)
{
@autoreleasepool {
MTLDepthStencilDescriptor *desc = (MTLDepthStencilDescriptor *)descriptor;
desc.depthCompareFunction = (MTLCompareFunction)vk_compare_op_to_mtl_compare_function(op);
}
}
void
mtl_depth_stencil_descriptor_set_depth_write_enabled(mtl_depth_stencil_descriptor *descriptor, bool enable_write)
{
@autoreleasepool {
MTLDepthStencilDescriptor *desc = (MTLDepthStencilDescriptor *)descriptor;
desc.depthWriteEnabled = enable_write;
}
}
void
mtl_depth_stencil_descriptor_set_back_face_stencil(mtl_depth_stencil_descriptor *descriptor, mtl_stencil_descriptor *stencil_descriptor)
{
@autoreleasepool {
MTLDepthStencilDescriptor *desc = (MTLDepthStencilDescriptor *)descriptor;
desc.backFaceStencil = (MTLStencilDescriptor *)stencil_descriptor;
}
}
void
mtl_depth_stencil_descriptor_set_front_face_stencil(mtl_depth_stencil_descriptor *descriptor, mtl_stencil_descriptor *stencil_descriptor)
{
@autoreleasepool {
MTLDepthStencilDescriptor *desc = (MTLDepthStencilDescriptor *)descriptor;
desc.frontFaceStencil = (MTLStencilDescriptor *)stencil_descriptor;
}
}
mtl_depth_stencil_state *
mtl_new_depth_stencil_state(mtl_device *device, mtl_depth_stencil_descriptor *descriptor)
{
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
MTLDepthStencilDescriptor *desc = (MTLDepthStencilDescriptor *)descriptor;
return [dev newDepthStencilStateWithDescriptor:desc];
}
}
@@ -0,0 +1,21 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef MTL_RESIDENCY_SET_H
#define MTL_RESIDENCY_SET_H 1
#include "mtl_types.h"
mtl_residency_set *mtl_new_residency_set(mtl_device *device);
void mtl_residency_set_add_allocation(mtl_residency_set *residency_set,
mtl_allocation *allocation);
void mtl_residency_set_remove_allocation(mtl_residency_set *residency_set,
mtl_allocation *allocation);
void mtl_residency_set_commit(mtl_residency_set *residency_set);
void mtl_residency_set_request_residency(mtl_residency_set *residency_set);
void mtl_residency_set_end_residency(mtl_residency_set *residency_set);
#endif /* MTL_RESIDENCY_SET_H */
@@ -0,0 +1,78 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_residency_set.h"
#include <Metal/MTLDevice.h>
#include <Metal/MTLResidencySet.h>
mtl_residency_set *
mtl_new_residency_set(mtl_device *device)
{
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
MTLResidencySetDescriptor *setDescriptor = [[[MTLResidencySetDescriptor alloc] init] autorelease];
setDescriptor.initialCapacity = 100;
NSError *error;
id<MTLResidencySet> set = [dev newResidencySetWithDescriptor:setDescriptor
error:&error];
if (error != nil) {
fprintf(stderr, "Failed to create MTLResidencySet: %s\n", [error.localizedDescription UTF8String]);
}
return set;
}
}
void
mtl_residency_set_add_allocation(mtl_residency_set *residency_set,
mtl_allocation *allocation)
{
@autoreleasepool {
id<MTLResidencySet> set = (id<MTLResidencySet>)residency_set;
id<MTLAllocation> alloc = (id<MTLAllocation>)allocation;
[set addAllocation:alloc];
}
}
void
mtl_residency_set_remove_allocation(mtl_residency_set *residency_set,
mtl_allocation *allocation)
{
@autoreleasepool {
id<MTLResidencySet> set = (id<MTLResidencySet>)residency_set;
id<MTLAllocation> alloc = (id<MTLAllocation>)allocation;
[set removeAllocation:alloc];
}
}
void
mtl_residency_set_commit(mtl_residency_set *residency_set)
{
@autoreleasepool {
id<MTLResidencySet> set = (id<MTLResidencySet>)residency_set;
[set commit];
}
}
void
mtl_residency_set_request_residency(mtl_residency_set *residency_set)
{
@autoreleasepool {
id<MTLResidencySet> set = (id<MTLResidencySet>)residency_set;
[set requestResidency];
}
}
void
mtl_residency_set_end_residency(mtl_residency_set *residency_set)
{
@autoreleasepool {
id<MTLResidencySet> set = (id<MTLResidencySet>)residency_set;
[set endResidency];
}
}
+50
View File
@@ -0,0 +1,50 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef MTL_SAMPLER_H
#define MTL_SAMPLER_H 1
#include "mtl_types.h"
#include <inttypes.h>
#include <stdbool.h>
/* Sampler descriptor */
mtl_sampler_descriptor *mtl_new_sampler_descriptor(void);
/* Sampler descriptor utils */
void mtl_sampler_descriptor_set_normalized_coordinates(
mtl_sampler_descriptor *descriptor, bool normalized_coordinates);
void mtl_sampler_descriptor_set_address_mode(
mtl_sampler_descriptor *descriptor,
enum mtl_sampler_address_mode address_mode_u,
enum mtl_sampler_address_mode address_mode_v,
enum mtl_sampler_address_mode address_mode_w);
void
mtl_sampler_descriptor_set_border_color(mtl_sampler_descriptor *descriptor,
enum mtl_sampler_border_color color);
void
mtl_sampler_descriptor_set_filters(mtl_sampler_descriptor *descriptor,
enum mtl_sampler_min_mag_filter min_filter,
enum mtl_sampler_min_mag_filter mag_filter,
enum mtl_sampler_mip_filter mip_filter);
void mtl_sampler_descriptor_set_lod_clamp(mtl_sampler_descriptor *descriptor,
float min, float max);
void
mtl_sampler_descriptor_set_max_anisotropy(mtl_sampler_descriptor *descriptor,
uint64_t max);
void
mtl_sampler_descriptor_set_compare_function(mtl_sampler_descriptor *descriptor,
enum mtl_compare_function function);
/* Sampler */
mtl_sampler *mtl_new_sampler(mtl_device *device,
mtl_sampler_descriptor *descriptor);
/* Sampler utils */
uint64_t mtl_sampler_get_gpu_resource_id(mtl_sampler *sampler);
#endif /* MTL_SAMPLER_H */
+118
View File
@@ -0,0 +1,118 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_sampler.h"
#include <Metal/MTLSampler.h>
mtl_sampler_descriptor *
mtl_new_sampler_descriptor()
{
@autoreleasepool {
MTLSamplerDescriptor *descriptor = [MTLSamplerDescriptor new];
/* Set common variables we don't expose */
descriptor.lodAverage = false;
descriptor.supportArgumentBuffers = true;
return descriptor;
}
}
void
mtl_sampler_descriptor_set_normalized_coordinates(mtl_sampler_descriptor *descriptor, bool normalized_coordinates)
{
@autoreleasepool {
MTLSamplerDescriptor *desc = (MTLSamplerDescriptor *)descriptor;
desc.normalizedCoordinates = normalized_coordinates;
}
}
void
mtl_sampler_descriptor_set_address_mode(mtl_sampler_descriptor *descriptor,
enum mtl_sampler_address_mode address_mode_u,
enum mtl_sampler_address_mode address_mode_v,
enum mtl_sampler_address_mode address_mode_w)
{
@autoreleasepool {
MTLSamplerDescriptor *desc = (MTLSamplerDescriptor *)descriptor;
desc.sAddressMode = (MTLSamplerAddressMode)address_mode_u;
desc.tAddressMode = (MTLSamplerAddressMode)address_mode_v;
desc.rAddressMode = (MTLSamplerAddressMode)address_mode_w;
}
}
void
mtl_sampler_descriptor_set_border_color(mtl_sampler_descriptor *descriptor, enum mtl_sampler_border_color color)
{
@autoreleasepool {
MTLSamplerDescriptor *desc = (MTLSamplerDescriptor *)descriptor;
desc.borderColor = (MTLSamplerBorderColor)color;
}
}
void
mtl_sampler_descriptor_set_filters(mtl_sampler_descriptor *descriptor,
enum mtl_sampler_min_mag_filter min_filter,
enum mtl_sampler_min_mag_filter mag_filter,
enum mtl_sampler_mip_filter mip_filter)
{
@autoreleasepool {
MTLSamplerDescriptor *desc = (MTLSamplerDescriptor *)descriptor;
desc.minFilter = (MTLSamplerMinMagFilter)min_filter;
desc.magFilter = (MTLSamplerMinMagFilter)mag_filter;
desc.mipFilter = (MTLSamplerMipFilter)mip_filter;
}
}
void
mtl_sampler_descriptor_set_lod_clamp(mtl_sampler_descriptor *descriptor,
float min,
float max)
{
@autoreleasepool {
MTLSamplerDescriptor *desc = (MTLSamplerDescriptor *)descriptor;
desc.lodMinClamp = min;
desc.lodMaxClamp = max;
}
}
void
mtl_sampler_descriptor_set_max_anisotropy(mtl_sampler_descriptor *descriptor,
uint64_t max)
{
@autoreleasepool {
MTLSamplerDescriptor *desc = (MTLSamplerDescriptor *)descriptor;
desc.maxAnisotropy = max ? max : 1u; /* Metal requires a non-zero value */
}
}
void
mtl_sampler_descriptor_set_compare_function(mtl_sampler_descriptor *descriptor,
enum mtl_compare_function function)
{
@autoreleasepool {
MTLSamplerDescriptor *desc = (MTLSamplerDescriptor *)descriptor;
desc.compareFunction = (MTLCompareFunction)function;
}
}
mtl_sampler *
mtl_new_sampler(mtl_device *device, mtl_sampler_descriptor *descriptor)
{
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
MTLSamplerDescriptor *desc = (MTLSamplerDescriptor *)descriptor;
return [dev newSamplerStateWithDescriptor:desc];
}
}
uint64_t
mtl_sampler_get_gpu_resource_id(mtl_sampler *sampler)
{
@autoreleasepool {
id<MTLSamplerState> samp = (id<MTLSamplerState>)sampler;
return [samp gpuResourceID]._impl;
}
}
+29
View File
@@ -0,0 +1,29 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef MTL_SYNC_H
#define MTL_SYNC_H 1
#include "mtl_types.h"
#include <inttypes.h>
/* MTLFence */
mtl_fence *mtl_new_fence(mtl_device *device);
/* MTLEvent */
mtl_event *mtl_new_event(mtl_device *device);
/* MTLSharedEvent */
mtl_shared_event *mtl_new_shared_event(mtl_device *device);
int mtl_shared_event_wait_until_signaled_value(mtl_shared_event *event_handle,
uint64_t value,
uint64_t timeout_ms);
uint64_t mtl_shared_event_get_signaled_value(mtl_shared_event *event_handle);
void mtl_shared_event_set_signaled_value(mtl_shared_event *event_handle,
uint64_t value);
#endif /* MTL_SYNC_H */
+66
View File
@@ -0,0 +1,66 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_sync.h"
#include <Metal/MTLEvent.h>
/* MTLFence */
mtl_fence *
mtl_new_fence(mtl_device *device)
{
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
return (mtl_fence *)[dev newFence];
}
}
/* MTLEvent */
mtl_event *
mtl_new_event(mtl_device *device)
{
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
return [dev newEvent];
}
}
/* MTLSharedEvent */
mtl_shared_event *
mtl_new_shared_event(mtl_device *device)
{
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
return [dev newSharedEvent];
}
}
int
mtl_shared_event_wait_until_signaled_value(mtl_shared_event *event_handle, uint64_t value, uint64_t timeout_ms)
{
@autoreleasepool {
id<MTLSharedEvent> event = (id<MTLSharedEvent>)event_handle;
return (int)[event waitUntilSignaledValue:value timeoutMS:timeout_ms];
}
}
void
mtl_shared_event_set_signaled_value(mtl_shared_event *event_handle, uint64_t value)
{
@autoreleasepool {
id<MTLSharedEvent> event = (id<MTLSharedEvent>)event_handle;
event.signaledValue = value;
}
}
uint64_t
mtl_shared_event_get_signaled_value(mtl_shared_event *event_handle)
{
@autoreleasepool {
id<MTLSharedEvent> event = (id<MTLSharedEvent>)event_handle;
return event.signaledValue;
}
}
+33
View File
@@ -0,0 +1,33 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef MTL_TEXTURE_H
#define MTL_TEXTURE_H 1
#include "mtl_types.h"
#include <inttypes.h>
/* TODO_KOSMICKRISP Move this to bridge. */
struct kk_view_layout;
/* Utils*/
uint64_t mtl_texture_get_gpu_resource_id(mtl_texture *texture);
/* Texture view creation */
mtl_texture *mtl_new_texture_view_with(mtl_texture *texture,
const struct kk_view_layout *layout);
mtl_texture *
mtl_new_texture_view_with_no_swizzle(mtl_texture *texture,
const struct kk_view_layout *layout);
void mtl_texture_get_bytes(mtl_texture *texture, void *host_ptr,
struct mtl_texture_memory_copy *data);
void mtl_texture_replace_region(mtl_texture *texture, const void *host_ptr,
struct mtl_texture_memory_copy *data);
#endif /* MTL_TEXTURE_H */
+127
View File
@@ -0,0 +1,127 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_texture.h"
/* TODO_LUNARG Remove */
#include "kk_image_layout.h"
/* TODO_LUNARG Remove */
#include "vulkan/vulkan.h"
#include <Metal/MTLTexture.h>
uint64_t
mtl_texture_get_gpu_resource_id(mtl_texture *texture)
{
@autoreleasepool {
id<MTLTexture> tex = (id<MTLTexture>)texture;
return (uint64_t)[tex gpuResourceID]._impl;
}
}
/* TODO_KOSMICKRISP This should be part of the mapping */
static uint32_t
mtl_texture_view_type(uint32_t type, uint8_t sample_count)
{
switch (type) {
case VK_IMAGE_VIEW_TYPE_1D:
return MTLTextureType1D;
case VK_IMAGE_VIEW_TYPE_1D_ARRAY:
return MTLTextureType1DArray;
case VK_IMAGE_VIEW_TYPE_2D:
return sample_count > 1u ? MTLTextureType2DMultisample : MTLTextureType2D;;
case VK_IMAGE_VIEW_TYPE_CUBE:
return MTLTextureTypeCube;
case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY:
return MTLTextureTypeCubeArray;
case VK_IMAGE_VIEW_TYPE_2D_ARRAY:
return sample_count > 1u ? MTLTextureType2DMultisampleArray : MTLTextureType2DArray;
case VK_IMAGE_VIEW_TYPE_3D:
return MTLTextureType3D;
default:
assert(false && "Unsupported VkViewType");
return MTLTextureType1D;
}
}
static MTLTextureSwizzle
mtl_texture_swizzle(enum pipe_swizzle swizzle)
{
const MTLTextureSwizzle map[] =
{
[PIPE_SWIZZLE_X] = MTLTextureSwizzleRed,
[PIPE_SWIZZLE_Y] = MTLTextureSwizzleGreen,
[PIPE_SWIZZLE_Z] = MTLTextureSwizzleBlue,
[PIPE_SWIZZLE_W] = MTLTextureSwizzleAlpha,
[PIPE_SWIZZLE_0] = MTLTextureSwizzleZero,
[PIPE_SWIZZLE_1] = MTLTextureSwizzleOne,
};
return map[swizzle];
}
mtl_texture *
mtl_new_texture_view_with(mtl_texture *texture, const struct kk_view_layout *layout)
{
@autoreleasepool {
id<MTLTexture> tex = (id<MTLTexture>)texture;
MTLTextureType type = mtl_texture_view_type(layout->view_type, layout->sample_count_sa);
NSRange levels = NSMakeRange(layout->base_level, layout->num_levels);
NSRange slices = NSMakeRange(layout->base_array_layer, layout->array_len);
MTLTextureSwizzleChannels swizzle = MTLTextureSwizzleChannelsMake(mtl_texture_swizzle(layout->swizzle.red),
mtl_texture_swizzle(layout->swizzle.green),
mtl_texture_swizzle(layout->swizzle.blue),
mtl_texture_swizzle(layout->swizzle.alpha));
return [tex newTextureViewWithPixelFormat:layout->format.mtl textureType:type levels:levels slices:slices swizzle:swizzle];
}
}
mtl_texture *
mtl_new_texture_view_with_no_swizzle(mtl_texture *texture, const struct kk_view_layout *layout)
{
@autoreleasepool {
id<MTLTexture> tex = (id<MTLTexture>)texture;
MTLTextureType type = mtl_texture_view_type(layout->view_type, layout->sample_count_sa);
NSRange levels = NSMakeRange(layout->base_level, layout->num_levels);
NSRange slices = NSMakeRange(layout->base_array_layer, layout->array_len);
return [tex newTextureViewWithPixelFormat:layout->format.mtl textureType:type levels:levels slices:slices];
}
}
void
mtl_texture_get_bytes(mtl_texture *texture, void *host_ptr,
struct mtl_texture_memory_copy *data)
{
@autoreleasepool {
id<MTLTexture> tex = (id<MTLTexture>)texture;
MTLRegion region = MTLRegionMake3D(data->image_origin.x, data->image_origin.y, data->image_origin.z,
data->image_size.x, data->image_size.y, data->image_size.z);
return [tex getBytes:host_ptr
bytesPerRow:data->buffer_stride_B
bytesPerImage:data->buffer_2d_image_size_B
fromRegion:region
mipmapLevel:data->image_level
slice:data->image_slice];
}
}
void
mtl_texture_replace_region(mtl_texture *texture, const void *host_ptr,
struct mtl_texture_memory_copy *data)
{
@autoreleasepool {
id<MTLTexture> tex = (id<MTLTexture>)texture;
MTLRegion region = MTLRegionMake3D(data->image_origin.x, data->image_origin.y, data->image_origin.z,
data->image_size.x, data->image_size.y, data->image_size.z);
return [tex replaceRegion:region
mipmapLevel:data->image_level
slice:data->image_slice
withBytes:host_ptr
bytesPerRow:data->buffer_stride_B
bytesPerImage:data->buffer_2d_image_size_B];
}
}
+293
View File
@@ -0,0 +1,293 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*
* Structures and enums found in this file are a 1-1 mapping of Metal's
* equivalents
*/
#ifndef KK_MTL_TYPES_H
#define KK_MTL_TYPES_H 1
#include <stddef.h> /* For size_t definition */
/** HANDLES */
typedef void mtl_device;
typedef void mtl_heap;
typedef void mtl_buffer;
typedef void mtl_texture;
typedef void mtl_command_queue;
typedef void mtl_command_buffer;
typedef void mtl_command_encoder;
typedef void mtl_blit_encoder;
typedef void mtl_compute_encoder;
typedef void mtl_render_encoder;
typedef void mtl_event;
typedef void mtl_shared_event;
typedef void mtl_sampler_descriptor;
typedef void mtl_sampler;
typedef void mtl_compute_pipeline_state;
typedef void mtl_library;
typedef void mtl_render_pipeline_state;
typedef void mtl_function;
typedef void mtl_resource;
typedef void mtl_render_pass_descriptor;
typedef void mtl_render_pipeline_descriptor;
typedef void mtl_fence;
typedef void mtl_stencil_descriptor;
typedef void mtl_depth_stencil_descriptor;
typedef void mtl_depth_stencil_state;
typedef void mtl_render_pass_attachment_descriptor;
typedef void mtl_residency_set;
typedef void mtl_allocation;
/** ENUMS */
enum mtl_cpu_cache_mode {
MTL_CPU_CACHE_MODE_DEFAULT_CACHE = 0,
MTL_CPU_CACHE_MODE_WRITE_COMBINED = 1,
};
enum mtl_storage_mode {
MTL_STORAGE_MODE_SHARED = 0,
MTL_STORAGE_MODE_MANAGED = 1,
MTL_STORAGE_MODE_PRIVATE = 2,
MTL_STORAGE_MODE_MEMORYLESS = 3,
};
enum mtl_hazard_tracking_mode {
MTL_HAZARD_TRACKING_MODE_DFEAULT = 0,
MTL_HAZARD_TRACKING_MODE_UNTRACKED = 1,
MTL_HAZARD_TRACKING_MODE_TRACKED = 2,
};
#define MTL_RESOURCE_CPU_CACHE_MODE_SHIFT 0
#define MTL_RESOURCE_STORAGE_MODE_SHIFT 4
#define MTL_RESOURCE_HAZARD_TRACKING_MODE_SHIFT 8
enum mtl_resource_options {
MTL_RESOURCE_CPU_CACHE_MODE_DEFAULT_CACHE =
MTL_CPU_CACHE_MODE_DEFAULT_CACHE << MTL_RESOURCE_CPU_CACHE_MODE_SHIFT,
MTL_RESOURCE_CPU_CACHE_MODE_WRITE_COMBINED =
MTL_CPU_CACHE_MODE_WRITE_COMBINED << MTL_RESOURCE_CPU_CACHE_MODE_SHIFT,
MTL_RESOURCE_STORAGE_MODE_SHARED = MTL_STORAGE_MODE_SHARED
<< MTL_RESOURCE_STORAGE_MODE_SHIFT,
MTL_RESOURCE_STORAGE_MODE_PRIVATE = MTL_STORAGE_MODE_PRIVATE
<< MTL_RESOURCE_STORAGE_MODE_SHIFT,
MTL_RESOURCE_TRACKING_MODE_DEFAULT =
MTL_HAZARD_TRACKING_MODE_DFEAULT
<< MTL_RESOURCE_HAZARD_TRACKING_MODE_SHIFT,
MTL_RESOURCE_TRACKING_MODE_UNTRACKED =
MTL_HAZARD_TRACKING_MODE_UNTRACKED
<< MTL_RESOURCE_HAZARD_TRACKING_MODE_SHIFT,
MTL_RESOURCE_TRACKING_MODE_TRACKED =
MTL_HAZARD_TRACKING_MODE_TRACKED
<< MTL_RESOURCE_HAZARD_TRACKING_MODE_SHIFT,
};
enum mtl_blit_options {
MTL_BLIT_OPTION_NONE = 0,
MTL_BLIT_OPTION_DEPTH_FROM_DEPTH_STENCIL = 1 << 0,
MTL_BLIT_OPTION_STENCIL_FROM_DEPTH_STENCIL = 1 << 1,
};
enum mtl_resource_usage {
MTL_RESOURCE_USAGE_READ = 1 << 0,
MTL_RESOURCE_USAGE_WRITE = 1 << 1,
};
enum mtl_primitive_type {
MTL_PRIMITIVE_TYPE_POINT = 0,
MTL_PRIMITIVE_TYPE_LINE = 1,
MTL_PRIMITIVE_TYPE_LINE_STRIP = 2,
MTL_PRIMITIVE_TYPE_TRIANGLE = 3,
MTL_PRIMITIVE_TYPE_TRIANGLE_STRIP = 4,
};
enum mtl_primitive_topology_class {
MTL_PRIMITIVE_TOPOLOGY_CLASS_UNSPECIFIED = 0,
MTL_PRIMITIVE_TOPOLOGY_CLASS_POINT = 1,
MTL_PRIMITIVE_TOPOLOGY_CLASS_LINE = 2,
MTL_PRIMITIVE_TOPOLOGY_CLASS_TRIANGLE = 3,
};
enum mtl_texture_type {
MTL_TEXTURE_TYPE_1D = 0u,
MTL_TEXTURE_TYPE_1D_ARRAY = 1u,
MTL_TEXTURE_TYPE_2D = 2u,
MTL_TEXTURE_TYPE_2D_ARRAY = 3u,
MTL_TEXTURE_TYPE_2D_MULTISAMPLE = 4u,
MTL_TEXTURE_TYPE_CUBE = 5u,
MTL_TEXTURE_TYPE_CUBE_ARRAY = 6u,
MTL_TEXTURE_TYPE_3D = 7u,
MTL_TEXTURE_TYPE_2D_ARRAY_MULTISAMPLE = 8u,
MTL_TEXTURE_TYPE_TEXTURE_BUFFER = 9u,
};
enum mtl_texture_usage {
MTL_TEXTURE_USAGE_UNKNOWN = 0x0000,
MTL_TEXTURE_USAGE_SHADER_READ = 0x0001,
MTL_TEXTURE_USAGE_SHADER_WRITE = 0X0002,
MTL_TEXTURE_USAGE_RENDER_TARGET = 0X0004,
MTL_TEXTURE_USAGE_PIXEL_FORMAT_VIEW = 0X0010,
MTL_TEXTURE_USAGE_SHADER_ATOMIC = 0X0020,
};
enum mtl_load_action {
MTL_LOAD_ACTION_DONT_CARE = 0u,
MTL_LOAD_ACTION_LOAD = 1u,
MTL_LOAD_ACTION_CLEAR = 2u,
};
enum mtl_store_action {
MTL_STORE_ACTION_DONT_CARE = 0u,
MTL_STORE_ACTION_STORE = 1u,
MTL_STORE_ACTION_MULTISAMPLE_RESOLVE = 2u,
MTL_STORE_ACTION_STORE_AND_MULTISAMPLE_RESOLVE = 3u,
MTL_STORE_ACTION_UNKNOWN = 4u,
MTL_STORE_ACTION_CUSTOM_SAMPLE_DEPTH_STORE = 5u,
};
enum mtl_texture_swizzle {
MTL_TEXTURE_SWIZZLE_ZERO = 0,
MTL_TEXTURE_SWIZZLE_ONE = 1,
MTL_TEXTURE_SWIZZLE_RED = 2,
MTL_TEXTURE_SWIZZLE_GREEN = 3,
MTL_TEXTURE_SWIZZLE_BLUE = 4,
MTL_TEXTURE_SWIZZLE_ALPHA = 5,
};
enum mtl_index_type {
MTL_INDEX_TYPE_UINT16 = 0,
MTL_INDEX_TYPE_UINT32 = 1,
};
enum mtl_sampler_address_mode {
MTL_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE = 0,
MTL_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE = 1,
MTL_SAMPLER_ADDRESS_MODE_REPEAT = 2,
MTL_SAMPLER_ADDRESS_MODE_MIRROR_REPEAT = 3,
MTL_SAMPLER_ADDRESS_MODE_CLAMP_TO_ZERO = 4,
MTL_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER_COLOR = 5,
};
enum mtl_sampler_border_color {
MTL_SAMPLER_BORDER_COLOR_TRANSPARENT_BLACK = 0,
MTL_SAMPLER_BORDER_COLOR_OPAQUE_BLACK = 1,
MTL_SAMPLER_BORDER_COLOR_OPAQUE_WHITE = 2,
};
enum mtl_sampler_min_mag_filter {
MTL_SAMPLER_MIN_MAG_FILTER_NEAREST = 0,
MTL_SAMPLER_MIN_MAG_FILTER_LINEAR = 1,
};
enum mtl_sampler_mip_filter {
MTL_SAMPLER_MIP_FILTER_NOT_MIP_MAPPED = 0,
MTL_SAMPLER_MIP_FILTER_NEAREST = 1,
MTL_SAMPLER_MIP_FILTER_LINEAR = 2,
};
enum mtl_compare_function {
MTL_COMPARE_FUNCTION_NEVER = 0,
MTL_COMPARE_FUNCTION_LESS = 1,
MTL_COMPARE_FUNCTION_EQUAL = 2,
MTL_COMPARE_FUNCTION_LESS_EQUAL = 3,
MTL_COMPARE_FUNCTION_GREATER = 4,
MTL_COMPARE_FUNCTION_NOT_EQUAL = 5,
MTL_COMPARE_FUNCTION_GREATER_EQUAL = 6,
MTL_COMPARE_FUNCTION_ALWAYS = 7,
};
enum mtl_winding {
MTL_WINDING_CLOCKWISE = 0,
MTL_WINDING_COUNTER_CLOCKWISE = 1,
};
enum mtl_cull_mode {
MTL_CULL_MODE_NONE = 0,
MTL_CULL_MODE_FRONT = 1,
MTL_CULL_MODE_BACK = 2,
};
enum mtl_visibility_result_mode {
MTL_VISIBILITY_RESULT_MODE_DISABLED = 0,
MTL_VISIBILITY_RESULT_MODE_BOOLEAN = 1,
MTL_VISIBILITY_RESULT_MODE_COUNTING = 2,
};
enum mtl_depth_clip_mode {
MTL_DEPTH_CLIP_MODE_CLIP = 0,
MTL_DEPTH_CLIP_MODE_CLAMP = 1,
};
enum mtl_barrier_scope {
MTL_BARRIER_SCOPE_BUFFERS = 1 << 0,
MTL_BARRIER_SCOPE_TEXTURES = 1 << 1,
MTL_BARRIER_SCOPE_RENDER_TARGETS = 1 << 2,
};
/** STRUCTURES */
struct mtl_range {
size_t offset;
size_t length;
};
struct mtl_origin {
size_t x, y, z;
};
struct mtl_size {
size_t x, y, z;
};
struct mtl_sample_position {
float x, y;
};
struct mtl_viewport {
double originX, originY, width, height, znear, zfar;
};
struct mtl_clear_color {
union {
struct {
double red, green, blue, alpha;
};
double channel[4];
};
};
struct mtl_scissor_rect {
size_t x, y, width, height;
};
struct mtl_texture_swizzle_channels {
enum mtl_texture_swizzle red;
enum mtl_texture_swizzle green;
enum mtl_texture_swizzle blue;
enum mtl_texture_swizzle alpha;
};
struct mtl_buffer_image_copy {
struct mtl_size image_size;
struct mtl_origin image_origin;
mtl_buffer *buffer;
mtl_texture *image;
size_t buffer_offset_B;
size_t buffer_stride_B;
size_t buffer_2d_image_size_B;
size_t image_slice;
size_t image_level;
enum mtl_blit_options options;
};
struct mtl_texture_memory_copy {
struct mtl_size image_size;
struct mtl_origin image_origin;
size_t buffer_stride_B;
size_t buffer_2d_image_size_B;
size_t image_slice;
size_t image_level;
};
#endif /* KK_MTL_TYPES_H */
+24
View File
@@ -0,0 +1,24 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_bridge.h"
mtl_texture *
mtl_drawable_get_texture(void *drawable_ptr)
{
return NULL;
}
void *
mtl_retain(void *handle)
{
return NULL;
}
void
mtl_release(void *handle)
{
}
+33
View File
@@ -0,0 +1,33 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_buffer.h"
uint64_t
mtl_buffer_get_length(mtl_buffer *buffer)
{
return 0u;
}
uint64_t
mtl_buffer_get_gpu_address(mtl_buffer *buffer)
{
return 0u;
}
void *
mtl_get_contents(mtl_buffer *buffer)
{
return NULL;
}
mtl_texture *
mtl_new_texture_with_descriptor_linear(mtl_buffer *buffer,
const struct kk_image_layout *layout,
uint64_t offset)
{
return NULL;
}
@@ -0,0 +1,35 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_command_buffer.h"
void
mtl_encode_signal_event(mtl_command_buffer *cmd_buf_handle,
mtl_event *event_handle, uint64_t value)
{
}
void
mtl_encode_wait_for_event(mtl_command_buffer *cmd_buf_handle,
mtl_event *event_handle, uint64_t value)
{
}
void
mtl_add_completed_handler(mtl_command_buffer *cmd, void (*callback)(void *data),
void *data)
{
}
void
mtl_command_buffer_commit(mtl_command_buffer *cmd_buf)
{
}
void
mtl_present_drawable(mtl_command_buffer *cmd_buf, void *drawable)
{
}
@@ -0,0 +1,31 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_command_queue.h"
mtl_command_queue *
mtl_new_command_queue(mtl_device *device, uint32_t cmd_buffer_count)
{
return NULL;
}
mtl_command_buffer *
mtl_new_command_buffer(mtl_command_queue *cmd_queue)
{
return NULL;
}
void
mtl_command_queue_add_residency_set(mtl_command_queue *cmd_queue,
mtl_residency_set *set)
{
}
void
mtl_command_queue_remove_residency_set(mtl_command_queue *cmd_queue,
mtl_residency_set *set)
{
}
@@ -0,0 +1,14 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_compute_state.h"
mtl_compute_pipeline_state *
mtl_new_compute_pipeline_state(mtl_device *device, mtl_function *function,
uint64_t max_total_threads_per_threadgroup)
{
return NULL;
}
+146
View File
@@ -0,0 +1,146 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_device.h"
/* Device creation */
mtl_device *
mtl_device_create(void)
{
return NULL;
}
/* Device operations */
void
mtl_start_gpu_capture(mtl_device *mtl_dev_handle, const char *directory)
{
}
void
mtl_stop_gpu_capture(void)
{
}
/* Device feature query */
void
mtl_device_get_name(mtl_device *dev, char buffer[256])
{
}
void
mtl_device_get_architecture_name(mtl_device *dev, char buffer[256])
{
}
uint64_t
mtl_device_get_peer_group_id(mtl_device *dev)
{
return 0u;
}
uint32_t
mtl_device_get_peer_index(mtl_device *dev)
{
return 0u;
}
uint64_t
mtl_device_get_registry_id(mtl_device *dev)
{
return 0u;
}
bool
mtl_device_supports_sample_count(mtl_device *dev, uint32_t sample_count)
{
return false;
}
struct mtl_size
mtl_device_max_threads_per_threadgroup(mtl_device *dev)
{
return (struct mtl_size){};
}
uint32_t
mtl_device_max_threadgroup_memory_length(mtl_device *dev)
{
return 0u;
}
uint64_t
mtl_device_max_buffer_length(mtl_device *dev)
{
return 0u;
}
uint64_t
mtl_device_recommended_max_working_set_size(mtl_device *dev)
{
return 0u;
}
uint64_t
mtl_device_current_allocated_size(mtl_device *dev)
{
return 0u;
}
/* Timestamp query */
uint64_t
mtl_device_get_gpu_timestamp(mtl_device *dev)
{
return 0u;
}
/* Resource queries */
void
mtl_heap_buffer_size_and_align_with_length(mtl_device *device, uint64_t *size_B,
uint64_t *align_B)
{
}
uint64_t
mtl_minimum_linear_texture_alignment_for_pixel_format(
mtl_device *device, enum mtl_pixel_format format)
{
return 0u;
}
void
mtl_heap_texture_size_and_align_with_descriptor(mtl_device *device,
struct kk_image_layout *layout,
uint64_t *size_B,
uint64_t *align_B)
{
}
uint32_t
mtl_sparse_tile_size_in_bytes(mtl_device *device)
{
return 0u;
}
struct mtl_size
mtl_sparse_tile_size(mtl_device *device, struct kk_image_layout *layout)
{
return (struct mtl_size){};
}
struct mtl_size
mtl_sparse_tile_count(mtl_device *device, struct kk_image_layout *layout,
struct mtl_size tile_size)
{
return (struct mtl_size){};
}
/* Resource creation */
mtl_buffer *
mtl_new_buffer_with_bytes_no_copy(mtl_device *device, void *ptr,
uint64_t size_B)
{
return NULL;
}
+278
View File
@@ -0,0 +1,278 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_encoder.h"
/* Common encoder utils */
void
mtl_end_encoding(void *encoder)
{
}
/* MTLBlitEncoder */
mtl_blit_encoder *
mtl_new_blit_command_encoder(mtl_command_buffer *cmd_buffer)
{
return NULL;
}
void
mtl_blit_update_fence(mtl_blit_encoder *encoder, mtl_fence *fence)
{
}
void
mtl_blit_wait_for_fence(mtl_blit_encoder *encoder, mtl_fence *fence)
{
}
void
mtl_copy_from_buffer_to_buffer(mtl_blit_encoder *blit_enc_handle,
mtl_buffer *src_buf, size_t src_offset,
mtl_buffer *dst_buf, size_t dst_offset,
size_t size)
{
}
void
mtl_copy_from_buffer_to_texture(mtl_blit_encoder *blit_enc_handle,
struct mtl_buffer_image_copy *data)
{
}
void
mtl_copy_from_texture_to_buffer(mtl_blit_encoder *blit_enc_handle,
struct mtl_buffer_image_copy *data)
{
}
void
mtl_copy_from_texture_to_texture(mtl_blit_encoder *blit_enc_handle,
mtl_texture *src_tex_handle, size_t src_slice,
size_t src_level, struct mtl_origin src_origin,
struct mtl_size src_size,
mtl_texture *dst_tex_handle, size_t dst_slice,
size_t dst_level, struct mtl_origin dst_origin)
{
}
void
mtl_memory_barrier_with_scope(mtl_compute_encoder *encoder,
enum mtl_barrier_scope scope)
{
}
/* MTLComputeEncoder */
mtl_compute_encoder *
mtl_new_compute_command_encoder(mtl_command_buffer *cmd_buffer)
{
return NULL;
}
void
mtl_compute_update_fence(mtl_compute_encoder *encoder, mtl_fence *fence)
{
}
void
mtl_compute_wait_for_fence(mtl_compute_encoder *encoder, mtl_fence *fence)
{
}
void
mtl_compute_set_pipeline_state(mtl_compute_encoder *encoder,
mtl_compute_pipeline_state *state_handle)
{
}
void
mtl_compute_set_buffer(mtl_compute_encoder *encoder, mtl_buffer *buffer,
size_t offset, size_t index)
{
}
void
mtl_compute_use_heaps(mtl_compute_encoder *encoder, mtl_heap **heaps,
uint32_t count)
{
}
void
mtl_dispatch_threads(mtl_compute_encoder *encoder, struct mtl_size grid_size,
struct mtl_size local_size)
{
}
void
mtl_dispatch_threadgroups_with_indirect_buffer(mtl_compute_encoder *encoder,
mtl_buffer *buffer,
uint32_t offset,
struct mtl_size local_size)
{
}
/* MTLRenderEncoder */
mtl_render_encoder *
mtl_new_render_command_encoder_with_descriptor(
mtl_command_buffer *command_buffer, mtl_render_pass_descriptor *descriptor)
{
return NULL;
}
void
mtl_render_update_fence(mtl_render_encoder *encoder, mtl_fence *fence)
{
}
void
mtl_render_wait_for_fence(mtl_render_encoder *encoder, mtl_fence *fence)
{
}
void
mtl_set_viewports(mtl_render_encoder *encoder, struct mtl_viewport *viewports,
uint32_t count)
{
}
void
mtl_set_scissor_rects(mtl_render_encoder *encoder,
struct mtl_scissor_rect *scissor_rects, uint32_t count)
{
}
void
mtl_render_set_pipeline_state(mtl_render_encoder *encoder,
mtl_render_pipeline_state *pipeline)
{
}
void
mtl_set_depth_stencil_state(mtl_render_encoder *encoder,
mtl_depth_stencil_state *state)
{
}
void
mtl_set_stencil_references(mtl_render_encoder *encoder, uint32_t front,
uint32_t back)
{
}
void
mtl_set_front_face_winding(mtl_render_encoder *encoder,
enum mtl_winding winding)
{
}
void
mtl_set_cull_mode(mtl_render_encoder *encoder, enum mtl_cull_mode mode)
{
}
void
mtl_set_visibility_result_mode(mtl_render_encoder *encoder,
enum mtl_visibility_result_mode mode,
size_t offset)
{
}
void
mtl_set_depth_bias(mtl_render_encoder *encoder, float depth_bias,
float slope_scale, float clamp)
{
}
void
mtl_set_depth_clip_mode(mtl_render_encoder *encoder,
enum mtl_depth_clip_mode mode)
{
}
void
mtl_set_vertex_amplification_count(mtl_render_encoder *encoder,
uint32_t *layer_ids, uint32_t id_count)
{
}
void
mtl_set_vertex_buffer(mtl_render_encoder *encoder, mtl_buffer *buffer,
uint32_t offset, uint32_t index)
{
}
void
mtl_set_vertex_bytes(mtl_render_encoder *encoder, const void *bytes,
uint32_t length, uint32_t index)
{
}
void
mtl_set_fragment_buffer(mtl_render_encoder *encoder, mtl_buffer *buffer,
uint32_t offset, uint32_t index)
{
}
void
mtl_set_fragment_bytes(mtl_render_encoder *encoder, const void *bytes,
uint32_t length, uint32_t index)
{
}
void
mtl_draw_primitives(mtl_render_encoder *encoder,
enum mtl_primitive_type primitve_type, uint32_t vertexStart,
uint32_t vertexCount, uint32_t instanceCount,
uint32_t baseInstance)
{
}
void
mtl_draw_indexed_primitives(
mtl_render_encoder *encoder, enum mtl_primitive_type primitve_type,
uint32_t index_count, enum mtl_index_type index_type,
mtl_buffer *index_buffer, uint32_t index_buffer_offset,
uint32_t instance_count, int32_t base_vertex, uint32_t base_instance)
{
}
void
mtl_draw_primitives_indirect(mtl_render_encoder *encoder,
enum mtl_primitive_type primitve_type,
mtl_buffer *indirect_buffer,
uint64_t indirect_buffer_offset)
{
}
void
mtl_draw_indexed_primitives_indirect(mtl_render_encoder *encoder,
enum mtl_primitive_type primitve_type,
enum mtl_index_type index_type,
mtl_buffer *index_buffer,
uint32_t index_buffer_offset,
mtl_buffer *indirect_buffer,
uint64_t indirect_buffer_offset)
{
}
void
mtl_render_use_resource(mtl_compute_encoder *encoder, mtl_resource *res_handle,
uint32_t usage)
{
}
void
mtl_render_use_resources(mtl_render_encoder *encoder,
mtl_resource **resource_handles, uint32_t count,
enum mtl_resource_usage usage)
{
}
void
mtl_render_use_heaps(mtl_render_encoder *encoder, mtl_heap **heaps,
uint32_t count)
{
}
+37
View File
@@ -0,0 +1,37 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_heap.h"
/* Creation */
mtl_heap *
mtl_new_heap(mtl_device *device, uint64_t size,
enum mtl_resource_options resource_options)
{
return NULL;
}
/* Utils */
uint64_t
mtl_heap_get_size(mtl_heap *heap)
{
return 0u;
}
/* Allocation from heap */
mtl_buffer *
mtl_new_buffer_with_length(mtl_heap *heap, uint64_t size_B, uint64_t offset_B)
{
return NULL;
}
mtl_texture *
mtl_new_texture_with_descriptor(mtl_heap *heap,
const struct kk_image_layout *layout,
uint64_t offset)
{
return NULL;
}
@@ -0,0 +1,19 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_library.h"
mtl_library *
mtl_new_library(mtl_device *device, const char *src)
{
return NULL;
}
mtl_function *
mtl_new_function_with_name(mtl_library *lib, const char *entry_point)
{
return NULL;
}
@@ -0,0 +1,295 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_render_state.h"
#include "mtl_format.h"
/* TODO_KOSMICKRISP Remove */
#include "vulkan/vulkan.h"
/* Render pass descriptor */
mtl_render_pass_descriptor *
mtl_new_render_pass_descriptor(void)
{
return NULL;
}
mtl_render_pass_attachment_descriptor *
mtl_render_pass_descriptor_get_color_attachment(
mtl_render_pass_descriptor *descriptor, uint32_t index)
{
return NULL;
}
mtl_render_pass_attachment_descriptor *
mtl_render_pass_descriptor_get_depth_attachment(
mtl_render_pass_descriptor *descriptor)
{
return NULL;
}
mtl_render_pass_attachment_descriptor *
mtl_render_pass_descriptor_get_stencil_attachment(
mtl_render_pass_descriptor *descriptor)
{
return NULL;
}
void
mtl_render_pass_attachment_descriptor_set_texture(
mtl_render_pass_attachment_descriptor *descriptor, mtl_texture *texture)
{
}
void
mtl_render_pass_attachment_descriptor_set_level(
mtl_render_pass_attachment_descriptor *descriptor, uint32_t level)
{
}
void
mtl_render_pass_attachment_descriptor_set_slice(
mtl_render_pass_attachment_descriptor *descriptor, uint32_t slice)
{
}
void
mtl_render_pass_attachment_descriptor_set_load_action(
mtl_render_pass_attachment_descriptor *descriptor,
enum mtl_load_action action)
{
}
void
mtl_render_pass_attachment_descriptor_set_store_action(
mtl_render_pass_attachment_descriptor *descriptor,
enum mtl_store_action action)
{
}
void
mtl_render_pass_attachment_descriptor_set_clear_color(
mtl_render_pass_attachment_descriptor *descriptor,
struct mtl_clear_color clear_color)
{
}
void
mtl_render_pass_attachment_descriptor_set_clear_depth(
mtl_render_pass_attachment_descriptor *descriptor, double depth)
{
}
void
mtl_render_pass_attachment_descriptor_set_clear_stencil(
mtl_render_pass_attachment_descriptor *descriptor, uint32_t stencil)
{
}
void
mtl_render_pass_descriptor_set_render_target_array_length(
mtl_render_pass_descriptor *descriptor, uint32_t length)
{
}
void
mtl_render_pass_descriptor_set_render_target_width(
mtl_render_pass_descriptor *descriptor, uint32_t width)
{
}
void
mtl_render_pass_descriptor_set_render_target_height(
mtl_render_pass_descriptor *descriptor, uint32_t height)
{
}
void
mtl_render_pass_descriptor_set_default_raster_sample_count(
mtl_render_pass_descriptor *descriptor, uint32_t sample_count)
{
}
void
mtl_render_pass_descriptor_set_sample_positions(
mtl_render_pass_descriptor *descriptor,
const struct mtl_sample_position *positions, uint32_t count)
{
}
void
mtl_render_pass_descriptor_set_visibility_buffer(
mtl_render_pass_descriptor *descriptor, mtl_buffer *visibility_buffer)
{
}
/* Render pipeline descriptor */
mtl_render_pipeline_descriptor *
mtl_new_render_pipeline_descriptor(void)
{
return NULL;
}
void
mtl_render_pipeline_descriptor_set_vertex_shader(
mtl_render_pass_descriptor *descriptor, mtl_function *shader)
{
}
void
mtl_render_pipeline_descriptor_set_fragment_shader(
mtl_render_pass_descriptor *descriptor, mtl_function *shader)
{
}
void
mtl_render_pipeline_descriptor_set_input_primitive_topology(
mtl_render_pass_descriptor *descriptor,
enum mtl_primitive_topology_class topology_class)
{
}
void
mtl_render_pipeline_descriptor_set_color_attachment_format(
mtl_render_pass_descriptor *descriptor, uint8_t index,
enum mtl_pixel_format format)
{
}
void
mtl_render_pipeline_descriptor_set_depth_attachment_format(
mtl_render_pass_descriptor *descriptor, enum mtl_pixel_format format)
{
}
void
mtl_render_pipeline_descriptor_set_stencil_attachment_format(
mtl_render_pass_descriptor *descriptor, enum mtl_pixel_format format)
{
}
void
mtl_render_pipeline_descriptor_set_raster_sample_count(
mtl_render_pass_descriptor *descriptor, uint32_t sample_count)
{
}
void
mtl_render_pipeline_descriptor_set_alpha_to_coverage(
mtl_render_pass_descriptor *descriptor, bool enabled)
{
}
void
mtl_render_pipeline_descriptor_set_alpha_to_one(
mtl_render_pass_descriptor *descriptor, bool enabled)
{
}
void
mtl_render_pipeline_descriptor_set_rasterization_enabled(
mtl_render_pass_descriptor *descriptor, bool enabled)
{
}
void
mtl_render_pipeline_descriptor_set_max_vertex_amplification_count(
mtl_render_pass_descriptor *descriptor, uint32_t count)
{
}
/* Render pipeline */
mtl_render_pipeline_state *
mtl_new_render_pipeline(mtl_device *device,
mtl_render_pass_descriptor *descriptor)
{
return NULL;
}
/* Stencil descriptor */
mtl_stencil_descriptor *
mtl_new_stencil_descriptor(void)
{
return NULL;
}
void
mtl_stencil_descriptor_set_stencil_failure_operation(
mtl_stencil_descriptor *descriptor, enum VkStencilOp op)
{
}
void
mtl_stencil_descriptor_set_depth_failure_operation(
mtl_stencil_descriptor *descriptor, enum VkStencilOp op)
{
}
void
mtl_stencil_descriptor_set_depth_stencil_pass_operation(
mtl_stencil_descriptor *descriptor, enum VkStencilOp op)
{
}
void
mtl_stencil_descriptor_set_stencil_compare_function(
mtl_stencil_descriptor *descriptor, enum VkCompareOp op)
{
}
void
mtl_stencil_descriptor_set_read_mask(mtl_stencil_descriptor *descriptor,
uint32_t mask)
{
}
void
mtl_stencil_descriptor_set_write_mask(mtl_stencil_descriptor *descriptor,
uint32_t mask)
{
}
/* Depth stencil descriptor */
mtl_depth_stencil_descriptor *
mtl_new_depth_stencil_descriptor(void)
{
return NULL;
}
void
mtl_depth_stencil_descriptor_set_depth_compare_function(
mtl_depth_stencil_descriptor *descriptor, enum VkCompareOp op)
{
}
void
mtl_depth_stencil_descriptor_set_depth_write_enabled(
mtl_depth_stencil_descriptor *descriptor, bool enable_write)
{
}
void
mtl_depth_stencil_descriptor_set_back_face_stencil(
mtl_depth_stencil_descriptor *descriptor,
mtl_stencil_descriptor *stencil_descriptor)
{
}
void
mtl_depth_stencil_descriptor_set_front_face_stencil(
mtl_depth_stencil_descriptor *descriptor,
mtl_stencil_descriptor *stencil_descriptor)
{
}
/* Depth stencil state */
mtl_depth_stencil_state *
mtl_new_depth_stencil_state(mtl_device *device,
mtl_depth_stencil_descriptor *descriptor)
{
return NULL;
}
@@ -0,0 +1,40 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_residency_set.h"
mtl_residency_set *
mtl_new_residency_set(mtl_device *device)
{
return NULL;
}
void
mtl_residency_set_add_allocation(mtl_residency_set *residency_set,
mtl_allocation *allocation)
{
}
void
mtl_residency_set_remove_allocation(mtl_residency_set *residency_set,
mtl_allocation *allocation)
{
}
void
mtl_residency_set_commit(mtl_residency_set *residency_set)
{
}
void
mtl_residency_set_request_residency(mtl_residency_set *residency_set)
{
}
void
mtl_residency_set_end_residency(mtl_residency_set *residency_set)
{
}
@@ -0,0 +1,74 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_sampler.h"
/* Sampler descriptor */
mtl_sampler_descriptor *
mtl_new_sampler_descriptor(void)
{
return NULL;
}
/* Sampler descriptor utils */
void
mtl_sampler_descriptor_set_normalized_coordinates(
mtl_sampler_descriptor *descriptor, bool normalized_coordinates)
{
}
void
mtl_sampler_descriptor_set_address_mode(
mtl_sampler_descriptor *descriptor,
enum mtl_sampler_address_mode address_mode_u,
enum mtl_sampler_address_mode address_mode_v,
enum mtl_sampler_address_mode address_mode_w)
{
}
void
mtl_sampler_descriptor_set_border_color(mtl_sampler_descriptor *descriptor,
enum mtl_sampler_border_color color)
{
}
void
mtl_sampler_descriptor_set_filters(mtl_sampler_descriptor *descriptor,
enum mtl_sampler_min_mag_filter min_filter,
enum mtl_sampler_min_mag_filter mag_filter,
enum mtl_sampler_mip_filter mip_filter)
{
}
void
mtl_sampler_descriptor_set_lod_clamp(mtl_sampler_descriptor *descriptor,
float min, float max)
{
}
void
mtl_sampler_descriptor_set_max_anisotropy(mtl_sampler_descriptor *descriptor,
uint64_t max)
{
}
void
mtl_sampler_descriptor_set_compare_function(mtl_sampler_descriptor *descriptor,
enum mtl_compare_function function)
{
}
/* Sampler */
mtl_sampler *
mtl_new_sampler(mtl_device *device, mtl_sampler_descriptor *descriptor)
{
return NULL;
}
/* Sampler utils */
uint64_t
mtl_sampler_get_gpu_resource_id(mtl_sampler *sampler)
{
return 0u;
}
+47
View File
@@ -0,0 +1,47 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_sync.h"
/* MTLFence */
mtl_fence *
mtl_new_fence(mtl_device *device)
{
return NULL;
}
/* MTLEvent */
mtl_event *
mtl_new_event(mtl_device *device)
{
return NULL;
}
/* MTLSharedEvent */
mtl_shared_event *
mtl_new_shared_event(mtl_device *device)
{
return NULL;
}
int
mtl_shared_event_wait_until_signaled_value(mtl_shared_event *event_handle,
uint64_t value, uint64_t timeout_ms)
{
return 0;
}
uint64_t
mtl_shared_event_get_signaled_value(mtl_shared_event *event_handle)
{
return 0u;
}
void
mtl_shared_event_set_signaled_value(mtl_shared_event *event_handle,
uint64_t value)
{
}
@@ -0,0 +1,41 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "mtl_texture.h"
/* Utils*/
uint64_t
mtl_texture_get_gpu_resource_id(mtl_texture *texture)
{
return 0u;
}
/* Texture view creation */
mtl_texture *
mtl_new_texture_view_with(mtl_texture *texture,
const struct kk_view_layout *layout)
{
return NULL;
}
mtl_texture *
mtl_new_texture_view_with_no_swizzle(mtl_texture *texture,
const struct kk_view_layout *layout)
{
return NULL;
}
void
mtl_texture_get_bytes(mtl_texture *texture, void *host_ptr,
struct mtl_texture_memory_copy *data)
{
}
void
mtl_texture_replace_region(mtl_texture *texture, const void *host_ptr,
struct mtl_texture_memory_copy *data)
{
}
+265
View File
@@ -0,0 +1,265 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "vk_to_mtl_map.h"
#include "kosmickrisp/bridge/mtl_types.h"
#include "util/format/u_format.h"
#include "vulkan/vulkan.h"
#include "vk_meta.h"
struct mtl_origin
vk_offset_3d_to_mtl_origin(const struct VkOffset3D *offset)
{
struct mtl_origin ret = {
.x = offset->x,
.y = offset->y,
.z = offset->z,
};
return ret;
}
struct mtl_size
vk_extent_3d_to_mtl_size(const struct VkExtent3D *extent)
{
struct mtl_size ret = {
.x = extent->width,
.y = extent->height,
.z = extent->depth,
};
return ret;
}
enum mtl_primitive_type
vk_primitive_topology_to_mtl_primitive_type(enum VkPrimitiveTopology topology)
{
switch (topology) {
case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
return MTL_PRIMITIVE_TYPE_POINT;
case VK_PRIMITIVE_TOPOLOGY_LINE_LIST:
return MTL_PRIMITIVE_TYPE_LINE;
case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:
return MTL_PRIMITIVE_TYPE_LINE_STRIP;
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch"
case VK_PRIMITIVE_TOPOLOGY_META_RECT_LIST_MESA:
#pragma GCC diagnostic pop
/* Triangle fans are emulated meaning we'll translate the index buffer to
* triangle list or generate a index buffer if there's none */
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN:
return MTL_PRIMITIVE_TYPE_TRIANGLE;
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:
return MTL_PRIMITIVE_TYPE_TRIANGLE_STRIP;
default:
assert(0 && "Primitive topology not supported!");
return 0;
}
}
enum mtl_primitive_topology_class
vk_primitive_topology_to_mtl_primitive_topology_class(
enum VkPrimitiveTopology topology)
{
switch (topology) {
case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
return MTL_PRIMITIVE_TOPOLOGY_CLASS_POINT;
case VK_PRIMITIVE_TOPOLOGY_LINE_LIST:
case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:
return MTL_PRIMITIVE_TOPOLOGY_CLASS_LINE;
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST:
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch"
case VK_PRIMITIVE_TOPOLOGY_META_RECT_LIST_MESA:
#pragma GCC diagnostic pop
return MTL_PRIMITIVE_TOPOLOGY_CLASS_TRIANGLE;
default:
return MTL_PRIMITIVE_TOPOLOGY_CLASS_UNSPECIFIED;
}
}
enum mtl_load_action
vk_attachment_load_op_to_mtl_load_action(enum VkAttachmentLoadOp op)
{
switch (op) {
case VK_ATTACHMENT_LOAD_OP_LOAD:
return MTL_LOAD_ACTION_LOAD;
case VK_ATTACHMENT_LOAD_OP_CLEAR:
return MTL_LOAD_ACTION_CLEAR;
case VK_ATTACHMENT_LOAD_OP_DONT_CARE:
return MTL_LOAD_ACTION_DONT_CARE;
default:
assert(false && "Unsupported VkAttachmentLoadOp");
return MTL_LOAD_ACTION_DONT_CARE;
};
}
enum mtl_store_action
vk_attachment_store_op_to_mtl_store_action(enum VkAttachmentStoreOp op)
{
switch (op) {
case VK_ATTACHMENT_STORE_OP_STORE:
return MTL_STORE_ACTION_STORE;
case VK_ATTACHMENT_STORE_OP_DONT_CARE:
return MTL_STORE_ACTION_DONT_CARE;
case VK_ATTACHMENT_STORE_OP_NONE:
return MTL_STORE_ACTION_UNKNOWN;
default:
assert(false && "Unsupported VkAttachmentStoreOp");
return MTL_STORE_ACTION_UNKNOWN;
};
}
enum mtl_sampler_address_mode
vk_sampler_address_mode_to_mtl_sampler_address_mode(
enum VkSamplerAddressMode mode)
{
switch (mode) {
case VK_SAMPLER_ADDRESS_MODE_REPEAT:
return MTL_SAMPLER_ADDRESS_MODE_REPEAT;
case VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT:
return MTL_SAMPLER_ADDRESS_MODE_MIRROR_REPEAT;
case VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE:
return MTL_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
case VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER:
return MTL_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER_COLOR;
case VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE:
return MTL_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
default:
UNREACHABLE("Unsupported address mode");
}
}
enum mtl_sampler_border_color
vk_border_color_to_mtl_sampler_border_color(enum VkBorderColor color)
{
switch (color) {
case VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK:
case VK_BORDER_COLOR_INT_TRANSPARENT_BLACK:
return MTL_SAMPLER_BORDER_COLOR_TRANSPARENT_BLACK;
case VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK:
case VK_BORDER_COLOR_INT_OPAQUE_BLACK:
return MTL_SAMPLER_BORDER_COLOR_OPAQUE_BLACK;
case VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE:
case VK_BORDER_COLOR_INT_OPAQUE_WHITE:
return MTL_SAMPLER_BORDER_COLOR_OPAQUE_WHITE;
case VK_BORDER_COLOR_FLOAT_CUSTOM_EXT:
case VK_BORDER_COLOR_INT_CUSTOM_EXT:
return MTL_SAMPLER_BORDER_COLOR_OPAQUE_WHITE;
default:
UNREACHABLE("Unsupported address mode");
}
}
enum mtl_sampler_min_mag_filter
vk_filter_to_mtl_sampler_min_mag_filter(enum VkFilter filter)
{
switch (filter) {
case VK_FILTER_NEAREST:
return MTL_SAMPLER_MIN_MAG_FILTER_NEAREST;
case VK_FILTER_LINEAR:
return MTL_SAMPLER_MIN_MAG_FILTER_LINEAR;
default:
UNREACHABLE("Unsupported address mode");
}
}
enum mtl_sampler_mip_filter
vk_sampler_mipmap_mode_to_mtl_sampler_mip_filter(enum VkSamplerMipmapMode mode)
{
switch (mode) {
case VK_SAMPLER_MIPMAP_MODE_NEAREST:
return MTL_SAMPLER_MIP_FILTER_NEAREST;
case VK_SAMPLER_MIPMAP_MODE_LINEAR:
return MTL_SAMPLER_MIP_FILTER_LINEAR;
default:
UNREACHABLE("Unsupported address mode");
}
}
enum mtl_compare_function
vk_compare_op_to_mtl_compare_function(enum VkCompareOp op)
{
switch (op) {
case VK_COMPARE_OP_NEVER:
return MTL_COMPARE_FUNCTION_NEVER;
case VK_COMPARE_OP_LESS:
return MTL_COMPARE_FUNCTION_LESS;
case VK_COMPARE_OP_EQUAL:
return MTL_COMPARE_FUNCTION_EQUAL;
case VK_COMPARE_OP_LESS_OR_EQUAL:
return MTL_COMPARE_FUNCTION_LESS_EQUAL;
case VK_COMPARE_OP_GREATER:
return MTL_COMPARE_FUNCTION_GREATER;
case VK_COMPARE_OP_NOT_EQUAL:
return MTL_COMPARE_FUNCTION_NOT_EQUAL;
case VK_COMPARE_OP_GREATER_OR_EQUAL:
return MTL_COMPARE_FUNCTION_GREATER_EQUAL;
case VK_COMPARE_OP_ALWAYS:
return MTL_COMPARE_FUNCTION_ALWAYS;
default:
UNREACHABLE("Unsupported address mode");
}
}
enum mtl_winding
vk_front_face_to_mtl_winding(enum VkFrontFace face)
{
switch (face) {
case VK_FRONT_FACE_CLOCKWISE:
return MTL_WINDING_CLOCKWISE;
case VK_FRONT_FACE_COUNTER_CLOCKWISE:
return MTL_WINDING_COUNTER_CLOCKWISE;
default:
assert(false && "Unsupported VkFrontFace");
return MTL_WINDING_CLOCKWISE;
}
}
enum mtl_cull_mode
vk_front_face_to_mtl_cull_mode(enum VkCullModeFlagBits mode)
{
switch (mode) {
case VK_CULL_MODE_NONE:
case VK_CULL_MODE_FRONT_AND_BACK: // Emulated with scissor
return MTL_CULL_MODE_NONE;
case VK_CULL_MODE_FRONT_BIT:
return MTL_CULL_MODE_FRONT;
case VK_CULL_MODE_BACK_BIT:
return MTL_CULL_MODE_BACK;
default:
UNREACHABLE("Unsupported VkCullModeFlags");
}
}
enum mtl_index_type
index_size_in_bytes_to_mtl_index_type(unsigned bytes)
{
switch (bytes) {
case 2u:
return MTL_INDEX_TYPE_UINT16;
case 4u:
return MTL_INDEX_TYPE_UINT32;
default:
UNREACHABLE("Unsupported byte size for index");
}
}
unsigned
mtl_index_type_to_size_B(enum mtl_index_type type)
{
switch (type) {
case MTL_INDEX_TYPE_UINT16:
return 2u;
case MTL_INDEX_TYPE_UINT32:
return 4u;
default:
UNREACHABLE("Unhandled index type");
}
}
+81
View File
@@ -0,0 +1,81 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_MTL_TO_VK_MAP_H
#define KK_MTL_TO_VK_MAP_H 1
struct mtl_origin;
struct mtl_size;
enum mtl_primitive_type;
enum mtl_primitive_topology_class;
enum mtl_load_action;
enum mtl_store_action;
enum mtl_sampler_address_mode;
enum mtl_sampler_border_color;
enum mtl_sampler_min_mag_filter;
enum mtl_sampler_mip_filter;
enum mtl_compare_function;
enum mtl_winding;
enum mtl_cull_mode;
enum mtl_index_type;
struct VkOffset3D;
struct VkExtent3D;
union VkClearColorValue;
enum VkPrimitiveTopology;
enum VkAttachmentLoadOp;
enum VkAttachmentStoreOp;
enum VkSamplerAddressMode;
enum VkBorderColor;
enum VkFilter;
enum VkSamplerMipmapMode;
enum VkCompareOp;
enum VkFrontFace;
enum VkCullModeFlagBits;
/* STRUCTS */
struct mtl_origin vk_offset_3d_to_mtl_origin(const struct VkOffset3D *offset);
struct mtl_size vk_extent_3d_to_mtl_size(const struct VkExtent3D *extent);
/* ENUMS */
enum mtl_primitive_type
vk_primitive_topology_to_mtl_primitive_type(enum VkPrimitiveTopology topology);
enum mtl_primitive_topology_class
vk_primitive_topology_to_mtl_primitive_topology_class(
enum VkPrimitiveTopology topology);
enum mtl_load_action
vk_attachment_load_op_to_mtl_load_action(enum VkAttachmentLoadOp op);
enum mtl_store_action
vk_attachment_store_op_to_mtl_store_action(enum VkAttachmentStoreOp op);
enum mtl_sampler_address_mode
vk_sampler_address_mode_to_mtl_sampler_address_mode(
enum VkSamplerAddressMode mode);
enum mtl_sampler_border_color
vk_border_color_to_mtl_sampler_border_color(enum VkBorderColor color);
enum mtl_sampler_min_mag_filter
vk_filter_to_mtl_sampler_min_mag_filter(enum VkFilter filter);
enum mtl_sampler_mip_filter
vk_sampler_mipmap_mode_to_mtl_sampler_mip_filter(enum VkSamplerMipmapMode mode);
enum mtl_compare_function
vk_compare_op_to_mtl_compare_function(enum VkCompareOp op);
enum mtl_winding vk_front_face_to_mtl_winding(enum VkFrontFace face);
enum mtl_cull_mode vk_front_face_to_mtl_cull_mode(enum VkCullModeFlagBits mode);
enum mtl_index_type index_size_in_bytes_to_mtl_index_type(unsigned bytes);
unsigned mtl_index_type_to_size_B(enum mtl_index_type type);
#endif /* KK_MTL_TO_VK_MAP_H */
+336
View File
@@ -0,0 +1,336 @@
/*
* Copyright 2026 LunarG, Inc.
* Copyright 2026 Google LLC
* Copyright 2023 Alyssa Rosenzweig
* Copyright 2020 Intel Corporation
* SPDX-License-Identifier: MIT
*/
#include "kosmickrisp/compiler/nir_to_msl.h"
#include "compiler/glsl_types.h"
#include "compiler/spirv/nir_spirv.h"
#include "nir.h"
#include "nir_builder.h"
#include "nir_builder_opcodes.h"
#include "nir_intrinsics.h"
#include "nir_precompiled.h"
#include "shader_enums.h"
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "util/macros.h"
#include <sys/mman.h>
static const struct spirv_to_nir_options spirv_options = {
.environment = NIR_SPIRV_OPENCL,
.shared_addr_format = nir_address_format_62bit_generic,
.global_addr_format = nir_address_format_62bit_generic,
.temp_addr_format = nir_address_format_62bit_generic,
.constant_addr_format = nir_address_format_64bit_global,
.create_library = true,
.printf = false, /* TODO_KOSMICKRISP Enable */
};
/* Standard optimization loop */
static void
optimize(nir_shader *nir)
{
bool progress;
do {
progress = false;
NIR_PASS(progress, nir, nir_split_var_copies);
NIR_PASS(progress, nir, nir_split_struct_vars, nir_var_function_temp);
NIR_PASS(progress, nir, nir_lower_var_copies);
NIR_PASS(progress, nir, nir_lower_vars_to_ssa);
NIR_PASS(progress, nir, nir_opt_copy_prop);
NIR_PASS(progress, nir, nir_opt_remove_phis);
NIR_PASS(progress, nir, nir_lower_all_phis_to_scalar);
NIR_PASS(progress, nir, nir_opt_dce);
NIR_PASS(progress, nir, nir_opt_dead_cf);
NIR_PASS(progress, nir, nir_opt_cse);
nir_opt_peephole_select_options peephole_select_options = {
.limit = 64,
.expensive_alu_ok = true,
};
NIR_PASS(progress, nir, nir_opt_peephole_select,
&peephole_select_options);
NIR_PASS(progress, nir, nir_opt_phi_precision);
NIR_PASS(progress, nir, nir_opt_algebraic);
NIR_PASS(progress, nir, nir_opt_constant_folding);
NIR_PASS(progress, nir, nir_opt_deref);
NIR_PASS(progress, nir, nir_opt_copy_prop_vars);
NIR_PASS(progress, nir, nir_opt_undef);
NIR_PASS(progress, nir, nir_lower_undef_to_zero);
NIR_PASS(progress, nir, nir_opt_shrink_vectors, true);
NIR_PASS(progress, nir, nir_opt_loop_unroll);
} while (progress);
}
static nir_shader *
compile(void *memctx, const uint32_t *spirv, size_t spirv_size)
{
const nir_shader_compiler_options *nir_options = &kk_nir_options;
assert(spirv_size % 4 == 0);
nir_shader *nir =
spirv_to_nir(spirv, spirv_size / 4, NULL, MESA_SHADER_KERNEL, "library",
&spirv_options, nir_options);
nir_validate_shader(nir, "after spirv_to_nir");
ralloc_steal(memctx, nir);
nir_fixup_is_exported(nir);
NIR_PASS(_, nir, nir_lower_system_values);
NIR_PASS(_, nir, nir_lower_calls_to_builtins);
nir_lower_compute_system_values_options cs = {.global_id_is_32bit = true};
NIR_PASS(_, nir, nir_lower_compute_system_values, &cs);
/* We have to lower away local constant initializers right before we
* inline functions. That way they get properly initialized at the top
* of the function and not at the top of its caller.
*/
NIR_PASS(_, nir, nir_lower_variable_initializers, nir_var_function_temp);
NIR_PASS(_, nir, nir_lower_returns);
NIR_PASS(_, nir, nir_inline_functions);
nir_remove_non_exported(nir);
NIR_PASS(_, nir, nir_opt_copy_prop);
NIR_PASS(_, nir, nir_opt_deref);
/* We can't deal with constant data, get rid of it */
nir_lower_constant_to_temp(nir);
/* We can go ahead and lower the rest of the constant initializers. We do
* this here so that nir_remove_dead_variables and split_per_member_structs
* below see the corresponding stores.
*/
NIR_PASS(_, nir, nir_lower_variable_initializers, ~0);
/* LLVM loves take advantage of the fact that vec3s in OpenCL are 16B
* aligned and so it can just read/write them as vec4s. This results in a
* LOT of vec4->vec3 casts on loads and stores. One solution to this
* problem is to get rid of all vec3 variables.
*/
NIR_PASS(_, nir, nir_lower_vec3_to_vec4,
nir_var_shader_temp | nir_var_function_temp | nir_var_mem_shared |
nir_var_mem_global | nir_var_mem_constant);
/* We assign explicit types early so that the optimizer can take advantage
* of that information and hopefully get rid of some of our memcpys.
*/
NIR_PASS(_, nir, nir_lower_vars_to_explicit_types,
nir_var_uniform | nir_var_shader_temp | nir_var_function_temp |
nir_var_mem_shared | nir_var_mem_global,
glsl_get_cl_type_size_align);
optimize(nir);
NIR_PASS(_, nir, nir_remove_dead_variables, nir_var_all, NULL);
/* Lower again, this time after dead-variables to get more compact variable
* layouts.
*/
NIR_PASS(_, nir, nir_lower_vars_to_explicit_types,
nir_var_shader_temp | nir_var_function_temp | nir_var_mem_shared |
nir_var_mem_global | nir_var_mem_constant,
glsl_get_cl_type_size_align);
assert(nir->constant_data_size == 0);
NIR_PASS(_, nir, nir_lower_memcpy);
NIR_PASS(_, nir, nir_lower_explicit_io, nir_var_mem_constant,
nir_address_format_64bit_global);
NIR_PASS(_, nir, nir_lower_explicit_io, nir_var_uniform,
nir_address_format_32bit_offset_as_64bit);
/* Note: we cannot lower explicit I/O here, because we need derefs in tact
* for function calls into the library to work.
*/
NIR_PASS(_, nir, nir_lower_convert_alu_types, NULL);
NIR_PASS(_, nir, nir_opt_if, 0);
NIR_PASS(_, nir, nir_opt_idiv_const, 16);
msl_lower_textures(nir);
optimize(nir);
return nir;
}
static void
print_shader(FILE *fp, const char *name, const char *suffix, uint32_t variant,
const char *msl, nir_shader *nir)
{
uint32_t msl_length = strlen(msl) + 1;
uint32_t workgroup_count_size = sizeof(nir->info.workgroup_size);
size_t sz_B = workgroup_count_size + msl_length;
size_t sz_el = DIV_ROUND_UP(sz_B, 4);
uint32_t *mem = calloc(sz_el, 4);
memcpy(mem, nir->info.workgroup_size, workgroup_count_size);
memcpy((uint8_t *)mem + workgroup_count_size, msl, msl_length);
nir_precomp_print_blob(fp, name, suffix, variant, mem, sz_B, true);
free(mem);
}
static nir_def *
load_kernel_input(nir_builder *b, unsigned num_components, unsigned bit_size,
unsigned offset_B)
{
nir_def *root = nir_load_buffer_ptr_kk(b, 1, 64, .binding = 0);
/* We've bound the address of the root descriptor, index in. */
nir_def *addr = nir_iadd(b, root, nir_imm_int64(b, offset_B));
return nir_build_load_global_constant(b, num_components, bit_size, addr,
.align_mul = bit_size,
.access = ACCESS_CAN_SPECULATE);
}
static int
type_size_vec4(const struct glsl_type *type, bool bindless)
{
return glsl_count_attribute_slots(type, false);
}
int
main(int argc, char **argv)
{
if (argc != 4) {
fprintf(stderr, "Usage: %s [input spir-v] [output header] [output C]\n",
argv[0]);
return 1;
}
const char *infile = argv[1];
const char *outh_file = argv[2];
const char *outc_file = argv[3];
void *mem_ctx = ralloc_context(NULL);
int fd = open(infile, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Failed to open %s\n", infile);
ralloc_free(mem_ctx);
return 1;
}
off_t spirv_len = lseek(fd, 0, SEEK_END);
const void *spirv_map = mmap(NULL, spirv_len, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
if (spirv_map == MAP_FAILED) {
fprintf(stderr, "Failed to mmap the file: errno=%d, %s\n", errno,
strerror(errno));
ralloc_free(mem_ctx);
return 1;
}
FILE *fp_h = fopen(outh_file, "w");
FILE *fp_c = fopen(outc_file, "w");
glsl_type_singleton_init_or_ref();
nir_precomp_print_header(fp_c, fp_h, "KosmicKrisp Contributors",
"libkk_shaders.h");
nir_shader *nir = compile(mem_ctx, spirv_map, spirv_len);
/* load_preamble works at 16-bit granularity */
struct nir_precomp_opts opt = {.arg_align_B = 2};
const char *target = "AppleSilicon";
nir_foreach_entrypoint(libfunc, nir) {
libfunc->pass_flags = 0;
unsigned nr_vars = nir_precomp_nr_variants(libfunc);
nir_precomp_print_layout_struct(fp_h, &opt, libfunc);
for (unsigned v = 0; v < nr_vars; ++v) {
nir_shader *s = nir_precompiled_build_variant(
libfunc, MESA_SHADER_COMPUTE, v, &kk_nir_options, &opt,
load_kernel_input);
nir_link_shader_functions(s, nir);
NIR_PASS(_, s, nir_inline_functions);
nir_remove_non_entrypoints(s);
NIR_PASS(_, s, nir_opt_deref);
NIR_PASS(_, s, nir_lower_vars_to_ssa);
NIR_PASS(_, s, nir_remove_dead_derefs);
NIR_PASS(_, s, nir_remove_dead_variables,
nir_var_function_temp | nir_var_shader_temp, NULL);
NIR_PASS(_, s, nir_lower_vars_to_explicit_types,
nir_var_shader_temp | nir_var_function_temp,
glsl_get_cl_type_size_align);
NIR_PASS(_, s, nir_lower_vars_to_explicit_types, nir_var_mem_shared,
glsl_get_cl_type_size_align);
/* Workgroup size cannot be lowered when the shader is in library form,
* so we need to lower compute system values again now that it is a
* variant with a distinct entry-point. */
NIR_PASS(_, s, nir_lower_compute_system_values, NULL);
NIR_PASS(_, s, nir_lower_explicit_io, nir_var_mem_shared,
nir_address_format_62bit_generic);
msl_preprocess_nir(s);
msl_preprocess_nir_workarounds(nir, 0);
msl_optimize_nir(nir);
NIR_PASS(_, s, nir_opt_deref);
NIR_PASS(_, s, nir_lower_vars_to_ssa);
NIR_PASS(_, s, nir_lower_explicit_io,
nir_var_shader_temp | nir_var_function_temp |
nir_var_mem_shared | nir_var_mem_global,
nir_address_format_62bit_generic);
/* nir_lower_explicit_io can create phis we need to get rid of */
NIR_PASS(_, s, nir_convert_from_ssa, true, true);
NIR_PASS(_, s, nir_trivialize_registers);
/* nir_lower_explicit_io will create unpack_64 we need to lower.
* Perform in a loop to make sure any optimizations that result in
* instructions we don't implement are fully lowered */
bool progress = false;
do {
progress = false;
NIR_PASS(progress, s, nir_opt_algebraic);
} while (progress);
nir_shader_gather_info(s, nir_shader_get_entrypoint(s));
struct nir_to_msl_options options = {};
const char *msl = nir_to_msl(s, &options);
print_shader(fp_c, libfunc->name, target, v, msl, s);
ralloc_free((void *)msl);
ralloc_free(s);
}
}
nir_precomp_print_program_enum(fp_h, nir, "libkk");
nir_precomp_print_dispatch_macros(fp_h, &opt, nir);
/* For each target, generate a table mapping programs to binaries */
nir_precomp_print_extern_binary_map(fp_h, "libkk", target);
nir_precomp_print_binary_map(fp_c, nir, "libkk", target, NULL);
glsl_type_singleton_decref();
fclose(fp_c);
fclose(fp_h);
ralloc_free(mem_ctx);
return 0;
}
@@ -0,0 +1,23 @@
/*
* Copyright 2026 LunarG, Inc.
* Copyright 2026 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_PRECOMPILED_SHADER_H
#define KK_PRECOMPILED_SHADER_H 1
#include "kosmickrisp/bridge/mtl_types.h"
#include <stdint.h>
struct kk_precompiled_info {
uint16_t workgroup_size[3];
};
struct kk_precompiled_shader {
struct kk_precompiled_info info;
mtl_compute_pipeline_state *pipeline;
};
#endif /* KK_PRECOMPILED_SHADER_H_ */
+13
View File
@@ -0,0 +1,13 @@
# Copyright 2026 LunarG, Inc.
# Copyright 2026 Google LLC
# SPDX-License-Identifier: MIT
prog_kk_clc = executable(
'kk_clc',
['kk_clc.c'],
link_with : [libmsl_compiler],
include_directories : [inc_include, inc_src],
c_args : [pre_args, no_override_init_args],
link_args : [ld_args_build_id],
dependencies : [idep_vtn, idep_nir, idep_mesautil],
)
+36
View File
@@ -0,0 +1,36 @@
# Copyright 2025 LunarG, Inc.
# Copyright 2025 Google LLC
# SPDX-License-Identifier: MIT
libmsl_compiler_files = files(
'nir_to_msl.c',
'msl_type_inference.c',
'msl_iomap.c',
'msl_nir_gather_common.c',
'msl_nir_lower_common.c',
'msl_nir_lower_subgroups.c',
)
msl_nir_algebraic_c = custom_target(
input : 'msl_nir_algebraic.py',
output : 'msl_nir_algebraic.c',
command : [
prog_python, '@INPUT@', '-p', dir_compiler_nir,
],
capture : true,
depend_files : nir_algebraic_depends,
)
libmsl_compiler = static_library(
'msl_compiler',
[libmsl_compiler_files, msl_nir_algebraic_c],
dependencies : [idep_nir, idep_mesautil],
gnu_symbol_visibility: 'hidden',
build_by_default: false,
)
idep_msl_to_nir = declare_dependency(
link_with : libmsl_compiler,
)
+535
View File
@@ -0,0 +1,535 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
/* This file primarily concerns itself with mapping from the NIR (and Vulkan)
* model of I/O to the Metal one. */
#include "msl_private.h"
#include "nir_builder.h"
/* Mapping from alu type to Metal scalar type */
static const char *
alu_type_to_string(nir_alu_type type)
{
switch (type) {
case nir_type_uint8:
return "uchar";
case nir_type_uint16:
return "ushort";
case nir_type_uint32:
return "uint";
case nir_type_uint64:
return "ulong";
case nir_type_int8:
return "char";
case nir_type_int16:
return "short";
case nir_type_int32:
return "int";
case nir_type_int64:
return "long";
case nir_type_float16:
return "half";
case nir_type_float32:
return "float";
case nir_type_bool8:
return "bool";
default:
UNREACHABLE("Unsupported nir_alu_type");
}
};
/* Type suffix for a vector of a given size. */
static const char *vector_suffixes[] = {
[1] = "",
[2] = "2",
[3] = "3",
[4] = "4",
};
/* The type names of the generated output structs */
static const char *VERTEX_OUTPUT_TYPE = "VertexOut";
static const char *FRAGMENT_OUTPUT_TYPE = "FragmentOut";
/* Mapping from NIR's varying slots to the generated struct member name */
static const struct {
const char *name;
bool user;
bool scalarized;
} VARYING_SLOT_INFO[NUM_TOTAL_VARYING_SLOTS] = {
[VARYING_SLOT_POS] = {"position"},
[VARYING_SLOT_PSIZ] = {"point_size"},
[VARYING_SLOT_PRIMITIVE_ID] = {"primitive_id"},
[VARYING_SLOT_LAYER] = {"render_target_array_index"},
[VARYING_SLOT_VIEWPORT] = {"viewport_array_index"},
[VARYING_SLOT_CLIP_DIST0] = {"clip_0", .user = true, .scalarized = true},
[VARYING_SLOT_CLIP_DIST1] = {"clip_1", .user = true, .scalarized = true},
[VARYING_SLOT_CULL_DIST0] = {"cull_0", .user = true, .scalarized = true},
[VARYING_SLOT_CULL_DIST1] = {"cull_1", .user = true, .scalarized = true},
[VARYING_SLOT_CULL_PRIMITIVE] = {"cull_primitive_0", .user = true,
.scalarized = true},
/* Using cull primitive slots to emulate cull distances in fragment shader,
* which may extend to one varying extra (which is otherwise unused) */
[VARYING_SLOT_CULL_PRIMITIVE + 1] = {"cull_primitive_1", .user = true,
.scalarized = true},
[VARYING_SLOT_VAR0] = {"vary_00", .user = true},
[VARYING_SLOT_VAR1] = {"vary_01", .user = true},
[VARYING_SLOT_VAR2] = {"vary_02", .user = true},
[VARYING_SLOT_VAR3] = {"vary_03", .user = true},
[VARYING_SLOT_VAR4] = {"vary_04", .user = true},
[VARYING_SLOT_VAR5] = {"vary_05", .user = true},
[VARYING_SLOT_VAR6] = {"vary_06", .user = true},
[VARYING_SLOT_VAR7] = {"vary_07", .user = true},
[VARYING_SLOT_VAR8] = {"vary_08", .user = true},
[VARYING_SLOT_VAR9] = {"vary_09", .user = true},
[VARYING_SLOT_VAR10] = {"vary_10", .user = true},
[VARYING_SLOT_VAR11] = {"vary_11", .user = true},
[VARYING_SLOT_VAR12] = {"vary_12", .user = true},
[VARYING_SLOT_VAR13] = {"vary_13", .user = true},
[VARYING_SLOT_VAR14] = {"vary_14", .user = true},
[VARYING_SLOT_VAR15] = {"vary_15", .user = true},
[VARYING_SLOT_VAR16] = {"vary_16", .user = true},
[VARYING_SLOT_VAR17] = {"vary_17", .user = true},
[VARYING_SLOT_VAR18] = {"vary_18", .user = true},
[VARYING_SLOT_VAR19] = {"vary_19", .user = true},
[VARYING_SLOT_VAR20] = {"vary_20", .user = true},
[VARYING_SLOT_VAR21] = {"vary_21", .user = true},
[VARYING_SLOT_VAR22] = {"vary_22", .user = true},
[VARYING_SLOT_VAR23] = {"vary_23", .user = true},
[VARYING_SLOT_VAR24] = {"vary_24", .user = true},
[VARYING_SLOT_VAR25] = {"vary_25", .user = true},
[VARYING_SLOT_VAR26] = {"vary_26", .user = true},
[VARYING_SLOT_VAR27] = {"vary_27", .user = true},
[VARYING_SLOT_VAR28] = {"vary_28", .user = true},
[VARYING_SLOT_VAR29] = {"vary_29", .user = true},
[VARYING_SLOT_VAR30] = {"vary_30", .user = true},
[VARYING_SLOT_VAR31] = {"vary_31", .user = true},
};
static void
varying_slot_name(struct nir_to_msl_ctx *ctx, unsigned location,
unsigned component)
{
if (VARYING_SLOT_INFO[location].scalarized) {
P(ctx, "%s_%c", VARYING_SLOT_INFO[location].name, "xyzw"[component]);
} else {
P(ctx, "%s", VARYING_SLOT_INFO[location].name);
}
}
static void
varying_slot_semantic(struct nir_to_msl_ctx *ctx, unsigned location,
unsigned component)
{
if (VARYING_SLOT_INFO[location].user) {
P(ctx, "[[user(");
varying_slot_name(ctx, location, component);
P(ctx, ")]]");
} else {
P(ctx, "[[");
varying_slot_name(ctx, location, component);
P(ctx, "]]");
}
}
/* Mapping from NIR fragment output slot to MSL struct member name */
static const char *FS_OUTPUT_NAME[] = {
[FRAG_RESULT_DEPTH] = "depth_out",
[FRAG_RESULT_STENCIL] = "stencil_out",
[FRAG_RESULT_SAMPLE_MASK] = "sample_mask_out",
[FRAG_RESULT_DATA0] = "color_0",
[FRAG_RESULT_DATA1] = "color_1",
[FRAG_RESULT_DATA2] = "color_2",
[FRAG_RESULT_DATA3] = "color_3",
[FRAG_RESULT_DATA4] = "color_4",
[FRAG_RESULT_DATA5] = "color_5",
[FRAG_RESULT_DATA6] = "color_6",
[FRAG_RESULT_DATA7] = "color_7",
};
/* Mapping from NIR fragment output slot to MSL struct member attribute */
static const char *FS_OUTPUT_SEMANTIC[] = {
[FRAG_RESULT_DEPTH] = "", // special case, depends on depth layout
[FRAG_RESULT_STENCIL] = "stencil", [FRAG_RESULT_SAMPLE_MASK] = "sample_mask",
[FRAG_RESULT_DATA0] = "color(0)", [FRAG_RESULT_DATA1] = "color(1)",
[FRAG_RESULT_DATA2] = "color(2)", [FRAG_RESULT_DATA3] = "color(3)",
[FRAG_RESULT_DATA4] = "color(4)", [FRAG_RESULT_DATA5] = "color(5)",
[FRAG_RESULT_DATA6] = "color(6)", [FRAG_RESULT_DATA7] = "color(7)",
};
const char *depth_layout_arg[8] = {
[FRAG_DEPTH_LAYOUT_ANY] = "any",
[FRAG_DEPTH_LAYOUT_GREATER] = "greater",
[FRAG_DEPTH_LAYOUT_LESS] = "less",
[FRAG_DEPTH_LAYOUT_UNCHANGED] = "any",
};
/* Generate the struct definition for the vertex shader return value */
static void
vs_output_block(nir_shader *shader, struct nir_to_msl_ctx *ctx)
{
P(ctx, "struct %s {\n", VERTEX_OUTPUT_TYPE);
ctx->indentlevel++;
u_foreach_bit64(location, shader->info.outputs_written) {
struct io_slot_info info = ctx->outputs_info[location];
bool scalarized = VARYING_SLOT_INFO[location].scalarized;
const char *type = alu_type_to_string(info.type);
const char *vector_suffix =
scalarized ? "" : vector_suffixes[info.num_components];
unsigned components = scalarized ? info.num_components : 1;
for (int c = 0; c < components; c++) {
P_IND(ctx, "%s%s ", type, vector_suffix);
varying_slot_name(ctx, location, c);
P(ctx, " ");
varying_slot_semantic(ctx, location, c);
P(ctx, ";\n");
}
}
if (shader->info.clip_distance_array_size)
P_IND(ctx, "float gl_ClipDistance [[clip_distance]] [%d];\n",
shader->info.clip_distance_array_size);
ctx->indentlevel--;
P(ctx, "};\n");
}
static void
fs_input_interpolant(struct nir_to_msl_ctx *ctx, uint64_t location)
{
struct io_slot_info info = ctx->inputs_info[location];
bool scalarized = VARYING_SLOT_INFO[location].scalarized;
const char *type = alu_type_to_string(info.type);
const char *vector_suffix =
scalarized ? "" : vector_suffixes[info.num_components];
unsigned components = scalarized ? info.num_components : 1;
const char *interp = "";
switch (info.interpolation) {
case INTERP_MODE_SMOOTH:
interp = "interpolation::perspective";
break;
case INTERP_MODE_NOPERSPECTIVE:
interp = "interpolation::no_perspective";
break;
default:
UNREACHABLE("Only perspective and no-perspective are allowed");
}
for (int c = 0; c < components; c++) {
P_IND(ctx, "interpolant<%s%s, %s> ", type, vector_suffix, interp);
varying_slot_name(ctx, location, c);
P(ctx, " ");
varying_slot_semantic(ctx, location, c);
P(ctx, ";\n");
}
}
static void
fs_input_no_interpolant(struct nir_to_msl_ctx *ctx, uint64_t location)
{
struct io_slot_info info = ctx->inputs_info[location];
bool scalarized = VARYING_SLOT_INFO[location].scalarized;
const char *type = alu_type_to_string(info.type);
const char *vector_suffix =
scalarized ? "" : vector_suffixes[info.num_components];
unsigned components = scalarized ? info.num_components : 1;
const char *interp = "";
switch (info.interpolation) {
case INTERP_MODE_NOPERSPECTIVE:
if (info.centroid)
interp = "[[centroid_no_perspective]]";
else if (info.sample)
interp = "[[sample_no_perspective]]";
else
interp = "[[center_no_perspective]]";
break;
case INTERP_MODE_FLAT:
interp = "[[flat]]";
break;
default:
if (info.centroid)
interp = "[[centroid_perspective]]";
else if (info.sample)
interp = "[[sample_perspective]]";
break;
}
for (int c = 0; c < components; c++) {
P_IND(ctx, "%s%s ", type, vector_suffix);
varying_slot_name(ctx, location, c);
P(ctx, " ");
varying_slot_semantic(ctx, location, c);
P(ctx, " %s;\n", interp);
}
}
/* Generate the struct definition for the fragment shader input argument */
static void
fs_input_block(nir_shader *shader, struct nir_to_msl_ctx *ctx)
{
P(ctx, "struct FragmentIn {\n");
ctx->indentlevel++;
u_foreach_bit64(location, shader->info.inputs_read) {
struct io_slot_info info = ctx->inputs_info[location];
if (info.uses_interpolant)
fs_input_interpolant(ctx, location);
else
fs_input_no_interpolant(ctx, location);
}
/* Enable reading from framebuffer */
u_foreach_bit64(location, shader->info.outputs_read) {
struct io_slot_info info = ctx->outputs_info[location];
const char *type = alu_type_to_string(info.type);
const char *vector_suffix = vector_suffixes[info.num_components];
P_IND(ctx, "%s%s ", type, vector_suffix);
P(ctx, "%s [[%s, raster_order_group(0)]];\n", FS_OUTPUT_NAME[location],
FS_OUTPUT_SEMANTIC[location]);
}
ctx->indentlevel--;
P(ctx, "};\n");
}
/* Generate the struct definition for the fragment shader return value */
static void
fs_output_block(nir_shader *shader, struct nir_to_msl_ctx *ctx)
{
P_IND(ctx, "struct %s {\n", FRAGMENT_OUTPUT_TYPE);
ctx->indentlevel++;
u_foreach_bit64(location, shader->info.outputs_written) {
struct io_slot_info info = ctx->outputs_info[location];
const char *type = alu_type_to_string(info.type);
const char *vector_suffix = vector_suffixes[info.num_components];
P_IND(ctx, "%s%s ", type, vector_suffix);
if (location == FRAG_RESULT_DEPTH) {
enum gl_frag_depth_layout depth_layout = shader->info.fs.depth_layout;
assert(depth_layout_arg[depth_layout]);
P(ctx, "%s [[depth(%s)]];\n", FS_OUTPUT_NAME[location],
depth_layout_arg[depth_layout]);
} else {
// TODO: scalarized fs outputs
P(ctx, "%s [[%s]];\n", FS_OUTPUT_NAME[location],
FS_OUTPUT_SEMANTIC[location]);
}
}
ctx->indentlevel--;
P_IND(ctx, "};\n")
}
struct gather_ctx {
struct io_slot_info *input;
struct io_slot_info *output;
};
static bool
msl_nir_gather_io_info(nir_builder *b, nir_intrinsic_instr *intrin, void *data)
{
struct gather_ctx *ctx = (struct gather_ctx *)data;
switch (intrin->intrinsic) {
case nir_intrinsic_load_interpolated_input: {
unsigned component = nir_intrinsic_component(intrin);
struct nir_io_semantics io = nir_intrinsic_io_semantics(intrin);
assert(io.num_slots == 1u && "We don't support arrays");
unsigned location = nir_src_as_uint(intrin->src[1u]) + io.location;
ctx->input[location].type = nir_intrinsic_dest_type(intrin);
ctx->input[location].num_components =
MAX2(ctx->input[location].num_components,
intrin->num_components + component);
assert(ctx->input[location].num_components <= 4u &&
"Cannot have more than a vec4");
nir_intrinsic_instr *interp_intrin =
nir_src_as_intrinsic(intrin->src[0u]);
ctx->input[location].interpolation =
nir_intrinsic_interp_mode(interp_intrin);
ctx->input[location].centroid =
interp_intrin->intrinsic == nir_intrinsic_load_barycentric_centroid;
ctx->input[location].sample =
interp_intrin->intrinsic == nir_intrinsic_load_barycentric_sample;
ctx->input[location].uses_interpolant |=
interp_intrin->intrinsic == nir_intrinsic_load_barycentric_at_sample ||
interp_intrin->intrinsic == nir_intrinsic_load_barycentric_at_offset;
break;
}
case nir_intrinsic_load_input: {
unsigned component = nir_intrinsic_component(intrin);
struct nir_io_semantics io = nir_intrinsic_io_semantics(intrin);
assert(io.num_slots == 1u && "We don't support arrays");
unsigned location = nir_src_as_uint(intrin->src[0u]) + io.location;
ctx->input[location].type = nir_intrinsic_dest_type(intrin);
ctx->input[location].interpolation = INTERP_MODE_FLAT;
ctx->input[location].num_components =
MAX2(ctx->input[location].num_components,
intrin->num_components + component);
assert(ctx->input[location].num_components <= 4u &&
"Cannot have more than a vec4");
break;
}
case nir_intrinsic_load_output: {
unsigned component = nir_intrinsic_component(intrin);
struct nir_io_semantics io = nir_intrinsic_io_semantics(intrin);
assert(io.num_slots == 1u && "We don't support arrays");
unsigned location = nir_src_as_uint(intrin->src[0u]) + io.location;
ctx->output[location].type = nir_intrinsic_dest_type(intrin);
ctx->output[location].num_components =
MAX2(ctx->output[location].num_components,
intrin->num_components + component);
assert(ctx->output[location].num_components <= 4u &&
"Cannot have more than a vec4");
break;
}
case nir_intrinsic_store_output: {
unsigned component = nir_intrinsic_component(intrin);
unsigned write_mask = nir_intrinsic_write_mask(intrin);
struct nir_io_semantics io = nir_intrinsic_io_semantics(intrin);
assert(io.num_slots == 1u && "We don't support arrays");
/* Due to nir_lower_blend that doesn't generate intrinsics with the same
* num_components as destination, we need to compute current store's
* num_components using offset and mask. */
unsigned num_components = component + 1u;
unsigned mask_left_most_index = 0u;
for (unsigned i = 0u; i < intrin->num_components; ++i) {
if ((write_mask >> i) & 1u)
mask_left_most_index = i;
}
num_components += mask_left_most_index;
unsigned location = nir_src_as_uint(intrin->src[1u]) + io.location;
ctx->output[location].type = nir_intrinsic_src_type(intrin);
ctx->output[location].num_components =
MAX3(ctx->output[location].num_components, num_components,
intrin->num_components);
assert(ctx->output[location].num_components <= 4u &&
"Cannot have more than a vec4");
break;
}
default:
break;
}
return false;
}
void
msl_gather_io_info(struct nir_to_msl_ctx *ctx,
struct io_slot_info *info_array_input,
struct io_slot_info *info_array_output)
{
struct gather_ctx gather_ctx = {
.input = info_array_input,
.output = info_array_output,
};
nir_shader_intrinsics_pass(ctx->shader, msl_nir_gather_io_info,
nir_metadata_all, &gather_ctx);
}
/* Generate all the struct definitions needed for shader I/O */
void
msl_emit_io_blocks(struct nir_to_msl_ctx *ctx, nir_shader *shader)
{
switch (ctx->shader->info.stage) {
case MESA_SHADER_VERTEX:
vs_output_block(shader, ctx);
break;
case MESA_SHADER_FRAGMENT:
fs_input_block(shader, ctx);
fs_output_block(shader, ctx);
break;
case MESA_SHADER_COMPUTE:
case MESA_SHADER_TESS_CTRL:
break;
default:
assert(0);
}
// TODO_KOSMICKRISP This should not exist. We need to create input structs in
// nir that will later be translated
P(ctx, "struct Buffer {\n");
ctx->indentlevel++;
P_IND(ctx, "uint64_t contents[1];\n"); // TODO_KOSMICKRISP This should not be
// a cpu pointer
ctx->indentlevel--;
P(ctx, "};\n")
P(ctx, "struct SamplerTable {\n");
ctx->indentlevel++;
P_IND(ctx, "sampler handles[1024];\n");
ctx->indentlevel--;
P(ctx, "};\n")
}
void
msl_emit_output_var(struct nir_to_msl_ctx *ctx, nir_shader *shader)
{
switch (shader->info.stage) {
case MESA_SHADER_VERTEX:
P_IND(ctx, "%s out = {};\n", VERTEX_OUTPUT_TYPE);
break;
case MESA_SHADER_FRAGMENT:
P_IND(ctx, "%s out = {};\n", FRAGMENT_OUTPUT_TYPE);
/* Load inputs to output */
u_foreach_bit64(location, shader->info.outputs_read) {
P_IND(ctx, "out.%s = in.%s;\n", FS_OUTPUT_NAME[location],
FS_OUTPUT_NAME[location]);
}
break;
default:
break;
}
}
void
msl_output_name(struct nir_to_msl_ctx *ctx, unsigned location,
unsigned component)
{
P(ctx, "out.")
switch (ctx->shader->info.stage) {
case MESA_SHADER_VERTEX:
varying_slot_name(ctx, location, component);
break;
case MESA_SHADER_FRAGMENT:
P(ctx, "%s", FS_OUTPUT_NAME[location]);
break;
default:
UNREACHABLE("Invalid shader stage");
}
}
void
msl_input_name(struct nir_to_msl_ctx *ctx, unsigned location,
unsigned component)
{
P(ctx, "in.");
switch (ctx->shader->info.stage) {
case MESA_SHADER_FRAGMENT:
varying_slot_name(ctx, location, component);
break;
default:
UNREACHABLE("Invalid shader stage");
}
}
uint32_t
msl_input_num_components(struct nir_to_msl_ctx *ctx, uint32_t location)
{
if (ctx->shader->info.stage == MESA_SHADER_FRAGMENT &&
VARYING_SLOT_INFO[location].scalarized)
return 1;
else
return ctx->inputs_info[location].num_components;
}
uint32_t
msl_output_num_components(struct nir_to_msl_ctx *ctx, uint32_t location)
{
if (ctx->shader->info.stage == MESA_SHADER_VERTEX &&
VARYING_SLOT_INFO[location].scalarized)
return 1;
else
return ctx->outputs_info[location].num_components;
}
@@ -0,0 +1,44 @@
# Copyright 2025 LunarG, Inc.
# Copyright 2025 Google LLC
# Copyright 2022 Alyssa Rosenzweig
# Copyright 2021 Collabora, Ltd.
# Copyright 2016 Intel Corporation
# SPDX-License-Identifier: MIT
import argparse
import sys
import math
a = 'a'
b = 'b'
lower = [
# Based on the VIR lowering
(('f2f16_rtz', 'a@32'),
('bcsel', ('flt', ('fabs', a), ('fabs', ('f2f32', ('f2f16_rtne', a)))),
('isub', ('f2f16_rtne', a), 1), ('f2f16_rtne', a))),
# Handle imod behavior with negative values using lowering
(('imod', a, b),
('bcsel', ('ior', ('ieq', ('irem', a, b), 0), ('ieq', ('isign', a), ('isign', b))),
('irem', a, b), ('iadd', ('irem', a, b), b))),
]
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--import-path', required=True)
args = parser.parse_args()
sys.path.insert(0, args.import_path)
run()
def run():
import nir_algebraic # pylint: disable=import-error
print('#include "msl_private.h"')
print(nir_algebraic.AlgebraicPass("msl_nir_lower_algebraic_late", lower).render())
if __name__ == '__main__':
main()
@@ -0,0 +1,24 @@
/*
* Copyright 2026 LunarG, Inc.
* Copyright 2026 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "msl_private.h"
#include "nir_to_msl.h"
#include "nir.h"
#include "nir_builder.h"
static bool
uses_per_draw_data(UNUSED nir_builder *b, nir_intrinsic_instr *intr,
UNUSED void *data)
{
return intr->intrinsic == nir_intrinsic_load_per_draw_ptr_kk;
}
bool
msl_gather_uses_per_draw_data(nir_shader *nir)
{
return nir_shader_intrinsics_pass(nir, uses_per_draw_data, nir_metadata_all,
NULL);
}
@@ -0,0 +1,598 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "msl_private.h"
#include "nir_to_msl.h"
#include "nir.h"
#include "nir_builder.h"
#include "util/format/u_format.h"
bool
msl_nir_vs_remove_point_size_write(nir_builder *b, nir_intrinsic_instr *intrin,
void *data)
{
if (intrin->intrinsic != nir_intrinsic_store_output)
return false;
nir_io_semantics io = nir_intrinsic_io_semantics(intrin);
if (io.location == VARYING_SLOT_PSIZ) {
return nir_remove_sysval_output(intrin, MESA_SHADER_FRAGMENT);
}
return false;
}
bool
msl_nir_fs_remove_depth_write(nir_builder *b, nir_intrinsic_instr *intrin,
void *data)
{
if (intrin->intrinsic != nir_intrinsic_store_output)
return false;
nir_io_semantics io = nir_intrinsic_io_semantics(intrin);
if (io.location == FRAG_RESULT_DEPTH) {
return nir_remove_sysval_output(intrin, MESA_SHADER_FRAGMENT);
}
return false;
}
static bool
fs_force_output_type(nir_builder *b, nir_intrinsic_instr *intrin, void *data)
{
if (intrin->intrinsic != nir_intrinsic_store_output)
return false;
nir_io_semantics io = nir_intrinsic_io_semantics(intrin);
if (io.location < FRAG_RESULT_DATA0 || FRAG_RESULT_DATA7 < io.location)
return false;
enum pipe_format *render_target_formats = (enum pipe_format *)data;
enum pipe_format format =
render_target_formats[io.location - FRAG_RESULT_DATA0];
nir_alu_type type = nir_intrinsic_src_type(intrin);
if (util_format_is_float(format) || util_format_is_unorm(format)) {
if (type & nir_type_uint) {
b->cursor = nir_before_instr(&intrin->instr);
nir_def *value =
nir_u2fN(b, intrin->src[0].ssa, nir_src_bit_size(intrin->src[0]));
nir_src_rewrite(&intrin->src[0], value);
type ^= (nir_type_float | nir_type_uint);
nir_intrinsic_set_src_type(intrin, type);
return true;
} else if (type & nir_type_int) {
b->cursor = nir_before_instr(&intrin->instr);
nir_def *value =
nir_u2fN(b, intrin->src[0].ssa, nir_src_bit_size(intrin->src[0]));
nir_src_rewrite(&intrin->src[0], value);
type ^= (nir_type_float | nir_type_int);
nir_intrinsic_set_src_type(intrin, type);
return true;
}
} else if (util_format_is_pure_sint(format)) {
if (type & nir_type_uint) {
type ^= (nir_type_uint | nir_type_int);
nir_intrinsic_set_src_type(intrin, type);
return true;
} else if (type & nir_type_float) {
b->cursor = nir_before_instr(&intrin->instr);
nir_def *value =
nir_f2uN(b, intrin->src[0].ssa, nir_src_bit_size(intrin->src[0]));
nir_src_rewrite(&intrin->src[0], value);
type ^= (nir_type_float | nir_type_int);
nir_intrinsic_set_src_type(intrin, type);
return true;
}
} else if (util_format_is_pure_uint(format)) {
if (type & nir_type_int) {
type ^= (nir_type_int | nir_type_uint);
nir_intrinsic_set_src_type(intrin, type);
return true;
} else if (type & nir_type_float) {
b->cursor = nir_before_instr(&intrin->instr);
nir_def *value =
nir_f2uN(b, intrin->src[0].ssa, nir_src_bit_size(intrin->src[0]));
nir_src_rewrite(&intrin->src[0], value);
type ^= (nir_type_float | nir_type_uint);
nir_intrinsic_set_src_type(intrin, type);
return true;
}
}
return false;
}
bool
msl_nir_fs_force_output_signedness(
nir_shader *nir, enum pipe_format render_target_formats[MAX_DRAW_BUFFERS])
{
return nir_shader_intrinsics_pass(nir, fs_force_output_type,
nir_metadata_control_flow,
render_target_formats);
}
bool
msl_lower_textures(nir_shader *nir)
{
bool progress = false;
nir_lower_tex_options lower_tex_options = {
.lower_txp = ~0u,
.lower_sampler_lod_bias = true,
/* We don't use 1D textures because they are really limited in Metal */
.lower_1d = true,
/* Metal does not support tg4 with individual offsets for each sample */
.lower_tg4_offsets = true,
/* Metal does not natively support offsets for texture.read operations */
.lower_txf_offset = true,
.lower_txd_cube_map = true,
};
NIR_PASS(progress, nir, nir_lower_tex, &lower_tex_options);
return progress;
}
static bool
replace_sample_id_for_sample_mask(nir_builder *b, nir_intrinsic_instr *intrin,
void *data)
{
if (intrin->intrinsic != nir_intrinsic_load_sample_mask_in)
return false;
nir_def_replace(nir_instr_def(&intrin->instr), (nir_def *)data);
return true;
}
static bool
msl_replace_load_sample_mask_in_for_static_sample_mask(
nir_builder *b, nir_intrinsic_instr *intr, void *data)
{
if (intr->intrinsic != nir_intrinsic_load_sample_mask_in)
return false;
b->cursor = nir_after_instr(&intr->instr);
nir_def *static_sample_mask = (nir_def *)data;
nir_def *sample_mask = nir_iand(b, &intr->def, static_sample_mask);
nir_def_rewrite_uses_after(&intr->def, sample_mask);
return true;
}
bool
msl_lower_static_sample_mask(nir_shader *nir, uint32_t sample_mask)
{
/* Only support vertex for now */
assert(nir->info.stage == MESA_SHADER_FRAGMENT);
/* Embed sample mask */
nir_function_impl *entrypoint = nir_shader_get_entrypoint(nir);
nir_builder b = nir_builder_at(nir_before_impl(entrypoint));
struct nir_io_semantics io_semantics = {
.location = FRAG_RESULT_SAMPLE_MASK,
.num_slots = 1u,
};
nir_def *static_sample_mask = nir_imm_int(&b, sample_mask);
nir_store_output(&b, nir_load_sample_mask_in(&b), nir_imm_int(&b, 0u),
.base = 0u, .range = 1u, .write_mask = 0x1, .component = 0u,
.src_type = nir_type_uint32, .io_semantics = io_semantics);
BITSET_SET(nir->info.system_values_read, SYSTEM_VALUE_SAMPLE_MASK_IN);
nir_shader_intrinsics_pass(
nir, msl_replace_load_sample_mask_in_for_static_sample_mask,
nir_metadata_control_flow, static_sample_mask);
return true;
}
bool
msl_ensure_depth_write(nir_shader *nir)
{
assert(nir->info.stage == MESA_SHADER_FRAGMENT);
bool has_depth_write =
nir->info.outputs_written & BITFIELD64_BIT(FRAG_RESULT_DEPTH);
if (!has_depth_write) {
nir_variable *depth_var = nir_create_variable_with_location(
nir, nir_var_shader_out, FRAG_RESULT_DEPTH, glsl_float_type());
/* Write to depth at the very beginning */
nir_function_impl *entrypoint = nir_shader_get_entrypoint(nir);
nir_builder b = nir_builder_at(nir_before_impl(entrypoint));
nir_deref_instr *depth_deref = nir_build_deref_var(&b, depth_var);
nir_def *position = nir_build_frag_coord(&b, 3);
nir_store_deref(&b, depth_deref, nir_channel(&b, position, 2u),
0xFFFFFFFF);
nir->info.outputs_written |= BITFIELD64_BIT(FRAG_RESULT_DEPTH);
nir->info.fs.depth_layout = FRAG_DEPTH_LAYOUT_ANY;
return nir_progress(true, entrypoint, nir_metadata_control_flow);
}
return false;
}
bool
msl_ensure_vertex_position_output(nir_shader *nir)
{
assert(nir->info.stage == MESA_SHADER_VERTEX);
bool has_position_write =
nir->info.outputs_written & BITFIELD64_BIT(VARYING_SLOT_POS);
if (!has_position_write) {
/* Write to depth at the very beginning */
nir_function_impl *entrypoint = nir_shader_get_entrypoint(nir);
nir_builder b = nir_builder_at(nir_before_impl(entrypoint));
struct nir_io_semantics io_semantics = {
.location = VARYING_SLOT_POS,
.num_slots = 4u,
};
nir_def *zero = nir_imm_float(&b, 0.0f);
nir_store_output(
&b, nir_vec4(&b, zero, zero, zero, zero), nir_imm_int(&b, 0u),
.base = 0u, .range = 4u, .write_mask = 0xf, .component = 0u,
.src_type = nir_type_float32, .io_semantics = io_semantics);
nir->info.outputs_written |= BITFIELD64_BIT(VARYING_SLOT_POS);
return nir_progress(true, entrypoint, nir_metadata_control_flow);
}
return false;
}
bool
msl_ensure_vertex_point_size_output(nir_shader *nir)
{
assert(nir->info.stage == MESA_SHADER_VERTEX);
bool has_point_size_write =
nir->info.outputs_written & BITFIELD64_BIT(VARYING_SLOT_PSIZ);
if (!has_point_size_write) {
nir_function_impl *entrypoint = nir_shader_get_entrypoint(nir);
nir_builder b = nir_builder_at(nir_before_impl(entrypoint));
struct nir_io_semantics io_semantics = {
.location = VARYING_SLOT_PSIZ,
.num_slots = 1u,
};
nir_store_output(&b, nir_imm_float(&b, 1.0f), nir_imm_int(&b, 0u),
.base = 0u, .range = 1u, .write_mask = 0x1,
.component = 0u, .src_type = nir_type_float32,
.io_semantics = io_semantics);
nir->info.outputs_written |= BITFIELD64_BIT(VARYING_SLOT_PSIZ);
return nir_progress(true, entrypoint, nir_metadata_control_flow);
}
return false;
}
static bool
msl_fs_io_types(nir_builder *b, nir_intrinsic_instr *intr, void *data)
{
if (intr->intrinsic == nir_intrinsic_load_input) {
struct nir_io_semantics io = nir_intrinsic_io_semantics(intr);
if (io.location == VARYING_SLOT_VIEWPORT) {
nir_intrinsic_set_dest_type(intr, nir_type_uint32);
return true;
}
}
if (intr->intrinsic == nir_intrinsic_store_output) {
struct nir_io_semantics io = nir_intrinsic_io_semantics(intr);
if (io.location == FRAG_RESULT_SAMPLE_MASK) {
nir_intrinsic_set_src_type(intr, nir_type_uint32);
return true;
} else if (io.location == FRAG_RESULT_STENCIL) {
nir_alu_type type = nir_intrinsic_src_type(intr);
nir_intrinsic_set_src_type(
intr, nir_type_uint | nir_alu_type_get_type_size(type));
return true;
}
}
if (intr->intrinsic == nir_intrinsic_load_output) {
struct nir_io_semantics io = nir_intrinsic_io_semantics(intr);
if (io.location == FRAG_RESULT_STENCIL) {
nir_alu_type type = nir_intrinsic_dest_type(intr);
nir_intrinsic_set_dest_type(
intr, nir_type_uint | nir_alu_type_get_type_size(type));
return true;
}
}
return false;
}
bool
msl_nir_fs_io_types(nir_shader *nir)
{
assert(nir->info.stage == MESA_SHADER_FRAGMENT);
return nir_shader_intrinsics_pass(nir, msl_fs_io_types, nir_metadata_all,
NULL);
}
static bool
msl_vs_io_types(nir_builder *b, nir_intrinsic_instr *intr, void *data)
{
if (intr->intrinsic == nir_intrinsic_store_output) {
struct nir_io_semantics io = nir_intrinsic_io_semantics(intr);
if (io.location == VARYING_SLOT_LAYER ||
io.location == VARYING_SLOT_VIEWPORT) {
nir_intrinsic_set_src_type(intr, nir_type_uint32);
return true;
}
}
return false;
}
bool
msl_nir_vs_io_types(nir_shader *nir)
{
assert(nir->info.stage == MESA_SHADER_VERTEX);
return nir_shader_intrinsics_pass(nir, msl_vs_io_types, nir_metadata_all,
NULL);
}
static bool
fake_guard_for_discards(nir_builder *b, nir_intrinsic_instr *intrin, void *data)
{
if (intrin->intrinsic != nir_intrinsic_demote)
return false;
b->cursor = nir_before_instr(&intrin->instr);
nir_def *helper = nir_is_helper_invocation(b, 1);
nir_demote_if(b, nir_inot(b, helper));
nir_instr_remove(&intrin->instr);
return true;
}
bool
msl_nir_fake_guard_for_discards(struct nir_shader *nir)
{
assert(nir->info.stage == MESA_SHADER_FRAGMENT);
/* No side effects, no lowering needed */
if (!nir->info.writes_memory)
return false;
return nir_shader_intrinsics_pass(nir, fake_guard_for_discards,
nir_metadata_control_flow, NULL);
}
/* Returns true if gl_SampleID is required. */
static bool
gather_fs_input_interpolant_usage(nir_builder *b, nir_intrinsic_instr *intr,
void *data)
{
if (intr->intrinsic != nir_intrinsic_load_interpolated_input)
return false;
bool *uses_interpolant = (bool *)data;
struct nir_io_semantics io = nir_intrinsic_io_semantics(intr);
nir_intrinsic_instr *interpolation = nir_src_as_intrinsic(intr->src[0]);
bool at_sample =
interpolation->intrinsic == nir_intrinsic_load_barycentric_at_sample;
uses_interpolant[io.location] |=
at_sample ||
interpolation->intrinsic == nir_intrinsic_load_barycentric_at_offset;
return at_sample;
}
static bool
lower_sample_shading(nir_builder *b, nir_intrinsic_instr *intr, void *data)
{
if (intr->intrinsic == nir_intrinsic_load_frag_coord) {
b->cursor = nir_after_instr(&intr->instr);
nir_def *offset =
nir_fadd(b, nir_load_sample_pos_from_id(b, 32u, nir_load_sample_id(b)),
nir_imm_vec2(b, -0.5f, -0.5f));
nir_def *sample_position =
nir_fadd(b, &intr->def, nir_pad_vector_imm_int(b, offset, 0u, 4u));
nir_def_rewrite_uses_after(&intr->def, sample_position);
return true;
}
if (intr->intrinsic == nir_intrinsic_load_sample_mask_in) {
b->cursor = nir_after_instr(&intr->instr);
nir_def *sample_id = nir_load_sample_id(b);
nir_def *sample_bit = nir_ishl(b, nir_imm_int(b, 1), sample_id);
nir_def *sample_mask_bit = nir_iand(b, &intr->def, sample_bit);
nir_def_rewrite_uses_after(&intr->def, sample_mask_bit);
return true;
}
if (intr->intrinsic != nir_intrinsic_load_interpolated_input)
return false;
bool *uses_interpolant = (bool *)data;
struct nir_io_semantics io = nir_intrinsic_io_semantics(intr);
nir_intrinsic_instr *interpolation = nir_src_as_intrinsic(intr->src[0]);
if (!uses_interpolant[io.location] ||
interpolation->intrinsic != nir_intrinsic_load_barycentric_sample)
return false;
b->cursor = nir_before_instr(&intr->instr);
nir_def *def = nir_load_barycentric_at_sample(
b, intr->def.bit_size, nir_load_sample_id(b),
.interp_mode = nir_intrinsic_interp_mode(interpolation));
nir_def_rewrite_uses_after(&interpolation->def, def);
nir_instr_remove(&interpolation->instr);
return false;
}
bool
msl_nir_lower_sample_shading(nir_shader *nir)
{
assert(nir->info.stage == MESA_SHADER_FRAGMENT);
bool uses_interpolant[NUM_TOTAL_VARYING_SLOTS] = {};
if (nir_shader_intrinsics_pass(nir, gather_fs_input_interpolant_usage,
nir_metadata_all, uses_interpolant))
BITSET_SET(nir->info.system_values_read, SYSTEM_VALUE_SAMPLE_ID);
return nir_shader_intrinsics_pass(
nir, lower_sample_shading, nir_metadata_control_flow, uses_interpolant);
}
static bool
lower_clip_cull_distance_write(nir_builder *b, nir_intrinsic_instr *intr,
UNUSED void *data)
{
if (intr->intrinsic != nir_intrinsic_store_output)
return false;
nir_io_semantics sem = nir_intrinsic_io_semantics(intr);
if (sem.location != VARYING_SLOT_CLIP_DIST0 &&
sem.location != VARYING_SLOT_CLIP_DIST1 &&
sem.location != VARYING_SLOT_CULL_DIST0 &&
sem.location != VARYING_SLOT_CULL_DIST1)
return false;
assert(nir_src_num_components(intr->src[0]) == 1 && "must be scalarized");
signed location = sem.location + nir_src_as_uint(intr->src[1]);
if (sem.location == VARYING_SLOT_CLIP_DIST0 ||
sem.location == VARYING_SLOT_CLIP_DIST1) {
/* Clip distance, add write to MSL clip_distance output */
unsigned component = (location - VARYING_SLOT_CLIP_DIST0) * 4 +
nir_intrinsic_component(intr);
b->cursor = nir_after_instr(&intr->instr);
nir_store_clip_distance_kk(b, intr->src[0].ssa, .base = component);
return true;
}
if (sem.location == VARYING_SLOT_CULL_DIST0 ||
sem.location == VARYING_SLOT_CULL_DIST1) {
/* Cull distance, add write to cull primitive output */
unsigned component = (location - VARYING_SLOT_CULL_DIST0) * 4 +
nir_intrinsic_component(intr);
b->cursor = nir_before_instr(&intr->instr);
nir_def *offs = nir_imm_int(b, component / 4);
nir_def *v = nir_b2f32(b, nir_fge_imm(b, intr->src[0].ssa, 0.0));
nir_store_output(b, v, offs, .component = component % 4,
.src_type = nir_type_float32,
.io_semantics.location = VARYING_SLOT_CULL_PRIMITIVE,
.io_semantics.num_slots = 2);
return true;
}
return false;
}
static bool
msl_nir_lower_clip_cull_distance_vs(nir_shader *s)
{
if (s->info.clip_distance_array_size == 0 &&
s->info.cull_distance_array_size == 0)
return false;
nir_shader_intrinsics_pass(s, lower_clip_cull_distance_write,
nir_metadata_control_flow, NULL);
if (s->info.cull_distance_array_size > 0)
s->info.outputs_written |=
BITFIELD64_RANGE(VARYING_SLOT_CULL_PRIMITIVE,
DIV_ROUND_UP(s->info.cull_distance_array_size, 4));
return true;
}
static bool
msl_nir_lower_cull_distance_fs(nir_shader *s, unsigned nr_distances)
{
assert(s->info.stage == MESA_SHADER_FRAGMENT);
if (nr_distances == 0)
return false;
nir_builder b_ =
nir_builder_at(nir_before_impl(nir_shader_get_entrypoint(s)));
nir_builder *b = &b_;
/* Test each half-space */
nir_def *culled = nir_imm_false(b);
for (unsigned i = 0; i < nr_distances; ++i) {
/* Load the cull primitive input for this cull distance */
nir_def *baryc = nir_load_barycentric_pixel(
b, 32, .interp_mode = INTERP_MODE_NOPERSPECTIVE);
nir_def *cull = nir_load_interpolated_input(
b, 1, 32, baryc, nir_imm_int(b, 0), .component = i & 3,
.io_semantics.location = VARYING_SLOT_CULL_PRIMITIVE + (i / 4),
.io_semantics.num_slots = nr_distances / 4);
/* When the cull distance is negative in the vertex shader, the resulting
* cull primitive output is zero, otherwise it is one. Thus, the
* interpolated value will be zero only if all of its vertices had
* negative cull distances, indicating the primitive should be called.
* Note that, since the value is interpolated at the pixel center, we
* don't have to worry about corner values. */
culled = nir_ior(b, culled, nir_ball(b, nir_feq_imm(b, cull, 0)));
}
/* Emulate primitive culling by discarding fragments */
nir_demote_if(b, culled);
s->info.inputs_read |= BITFIELD64_RANGE(VARYING_SLOT_CULL_PRIMITIVE,
DIV_ROUND_UP(nr_distances, 4));
s->info.fs.uses_discard = true;
return nir_progress(true, b->impl, nir_metadata_control_flow);
}
/* Scalarize stores to CLIP_DIST* varyings */
static bool
scalarize_clip_cull_distance_filter(const nir_intrinsic_instr *intrin,
UNUSED const void *_data)
{
if (intrin->intrinsic != nir_intrinsic_store_output)
return false;
nir_io_semantics semantics = nir_intrinsic_io_semantics(intrin);
return semantics.location == VARYING_SLOT_CLIP_DIST0 ||
semantics.location == VARYING_SLOT_CLIP_DIST1 ||
semantics.location == VARYING_SLOT_CULL_DIST0 ||
semantics.location == VARYING_SLOT_CULL_DIST1;
}
void
msl_nir_lower_clip_cull_distance(nir_shader *nir, unsigned num_cull_distances)
{
NIR_PASS(_, nir, nir_lower_io_to_scalar, nir_var_shader_out,
scalarize_clip_cull_distance_filter, NULL);
NIR_PASS(_, nir, nir_separate_merged_clip_cull_io);
if (nir->info.stage == MESA_SHADER_FRAGMENT)
NIR_PASS(_, nir, msl_nir_lower_cull_distance_fs, num_cull_distances);
else
NIR_PASS(_, nir, msl_nir_lower_clip_cull_distance_vs);
}
static bool
lower_instance_id(nir_builder *b, nir_intrinsic_instr *intr, UNUSED void *data)
{
if (intr->intrinsic != nir_intrinsic_load_instance_id)
return false;
b->cursor = nir_after_instr(&intr->instr);
nir_def *base_instance = nir_load_base_instance(b);
nir_def *instance_id = nir_isub(b, &intr->def, base_instance);
nir_def_rewrite_uses_after(&intr->def, instance_id);
BITSET_SET(b->shader->info.system_values_read, SYSTEM_VALUE_BASE_INSTANCE);
return true;
}
bool
msl_nir_lower_instance_id(nir_shader *nir)
{
return nir_shader_intrinsics_pass(nir, lower_instance_id,
nir_metadata_control_flow, NULL);
}
@@ -0,0 +1,76 @@
/*
* Copyright 2023 Valve Corporation
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "msl_private.h"
#include "nir.h"
#include "nir_builder.h"
static bool
needs_bool_widening(nir_intrinsic_instr *intrin)
{
switch (intrin->intrinsic) {
case nir_intrinsic_read_invocation:
case nir_intrinsic_read_first_invocation:
case nir_intrinsic_reduce:
case nir_intrinsic_quad_broadcast:
case nir_intrinsic_quad_swap_horizontal:
case nir_intrinsic_quad_swap_vertical:
case nir_intrinsic_quad_swap_diagonal:
case nir_intrinsic_rotate:
case nir_intrinsic_shuffle:
case nir_intrinsic_shuffle_down:
case nir_intrinsic_shuffle_up:
case nir_intrinsic_shuffle_xor:
return true;
default:
return false;
}
}
static bool
lower_bool_ops(nir_builder *b, nir_intrinsic_instr *intrin, void *_unused)
{
if (!needs_bool_widening(intrin))
return false;
if (intrin->def.bit_size != 1)
return false;
b->cursor = nir_before_instr(&intrin->instr);
nir_def *widen = nir_b2i32(b, intrin->src[0].ssa);
nir_src_rewrite(&intrin->src[0], widen);
intrin->def.bit_size = 32;
b->cursor = nir_after_instr(&intrin->instr);
nir_def *narrow = nir_b2b1(b, &intrin->def);
nir_def_rewrite_uses_after(&intrin->def, narrow);
return true;
}
void
msl_nir_lower_subgroups(nir_shader *nir)
{
const nir_lower_subgroups_options subgroups_options = {
.subgroup_size = 32,
.ballot_bit_size = 32,
.ballot_components = 1,
.lower_subgroup_masks = true,
.lower_vote_ieq = true,
.lower_vote_feq = true,
.lower_vote_bool_eq = true,
.lower_inverse_ballot = true,
/* Metal requires relative shuffle operations to have uniform delta */
.lower_relative_shuffle = true,
/* Metal reduce operations do not support certain types or cluster size */
.lower_reduce = true,
/* Metal rotate operations do not support cluster size */
.lower_rotate_clustered_to_shuffle = true,
};
NIR_PASS(_, nir, nir_lower_subgroups, &subgroups_options);
NIR_PASS(_, nir, nir_shader_intrinsics_pass, lower_bool_ops,
nir_metadata_control_flow, NULL);
}
+88
View File
@@ -0,0 +1,88 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "util/string_buffer.h"
#include "nir.h"
struct io_slot_info {
nir_alu_type type;
uint32_t interpolation;
unsigned num_components;
bool centroid;
bool sample;
bool uses_interpolant;
};
struct nir_to_msl_ctx {
FILE *output;
struct hash_table *types;
nir_shader *shader;
struct _mesa_string_buffer *text;
uint64_t disabled_workarounds;
bool uses_per_draw_data;
unsigned short indentlevel;
struct io_slot_info inputs_info[NUM_TOTAL_VARYING_SLOTS];
struct io_slot_info outputs_info[NUM_TOTAL_VARYING_SLOTS];
};
#define P_IND(ctx, ...) \
do { \
for (unsigned i = 0; i < (ctx)->indentlevel; i++) \
_mesa_string_buffer_append((ctx)->text, " "); \
_mesa_string_buffer_printf((ctx)->text, __VA_ARGS__); \
} while (0);
#define P(ctx, ...) _mesa_string_buffer_printf((ctx)->text, __VA_ARGS__);
#define P_INDENT(ctx) \
do { \
for (unsigned i = 0; i < (ctx)->indentlevel; i++) \
_mesa_string_buffer_append((ctx)->text, " "); \
} while (0)
/* Perform type inference. The returned value is a
* map from nir_def* to base type.*/
struct hash_table *msl_infer_types(nir_shader *shader);
const char *msl_type_for_def(struct hash_table *types, nir_def *def);
const char *msl_uint_type(uint8_t bit_size, uint8_t num_components);
const char *msl_type_for_src(struct hash_table *types, nir_src *src);
const char *msl_bitcast_for_src(struct hash_table *types, nir_src *src);
void msl_src_as_const(struct nir_to_msl_ctx *ctx, nir_src *src);
void msl_emit_io_blocks(struct nir_to_msl_ctx *ctx, nir_shader *shader);
void msl_emit_output_var(struct nir_to_msl_ctx *ctx, nir_shader *shader);
void msl_gather_io_info(struct nir_to_msl_ctx *ctx,
struct io_slot_info *info_array_input,
struct io_slot_info *info_array_output);
void msl_input_name(struct nir_to_msl_ctx *ctx, unsigned location,
unsigned component);
void msl_output_name(struct nir_to_msl_ctx *ctx, unsigned location,
unsigned component);
uint32_t msl_input_num_components(struct nir_to_msl_ctx *ctx,
uint32_t location);
uint32_t msl_output_num_components(struct nir_to_msl_ctx *ctx,
uint32_t location);
bool msl_src_is_float(struct nir_to_msl_ctx *ctx, nir_src *src);
bool msl_def_is_sampler(struct nir_to_msl_ctx *ctx, nir_def *def);
void msl_nir_lower_subgroups(nir_shader *nir);
bool msl_nir_lower_algebraic_late(nir_shader *shader);
@@ -0,0 +1,885 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "vulkan/vulkan_core.h"
#include "msl_private.h"
typedef enum ti_type {
/* We haven't been able to assign a type yet */
TYPE_NONE = 0,
/* All we know is that this is used in I/O, we
* can treat it as an opaque value (i.e. uint) */
TYPE_GENERIC_DATA,
/* A generic int used in ALU operations but also can a bool for bitwise ops */
TYPE_GENERIC_INT_OR_BOOL,
/* A generic int used in ALU operations that can be int or uint */
TYPE_GENERIC_INT,
/* These are actual concrete types. */
TYPE_INT,
TYPE_UINT,
TYPE_BOOL,
TYPE_FLOAT,
TYPE_SAMPLER,
} ti_type;
static ti_type
unify_types(ti_type t1, ti_type t2)
{
ti_type generic = MIN2(t1, t2);
ti_type specific = MAX2(t1, t2);
if (t1 == t2)
return TYPE_NONE;
// NONE or GENERIC_DATA can be upgraded into any concrete type
if (generic == TYPE_GENERIC_DATA || generic == TYPE_NONE)
return specific;
if ((generic == TYPE_GENERIC_INT_OR_BOOL) &&
((specific == TYPE_INT) || (specific == TYPE_UINT) ||
(specific == TYPE_BOOL)))
return specific;
if ((generic == TYPE_GENERIC_INT) &&
((specific == TYPE_INT) || (specific == TYPE_UINT)))
return specific;
return TYPE_NONE;
}
static ti_type
ti_type_from_nir(nir_alu_type nir_type)
{
switch (nir_alu_type_get_base_type(nir_type)) {
case nir_type_int:
return TYPE_INT;
case nir_type_uint:
return TYPE_UINT;
case nir_type_float:
return TYPE_FLOAT;
case nir_type_bool:
return TYPE_BOOL;
default:
assert(0);
return TYPE_NONE;
}
}
static ti_type
ti_type_from_pipe_format(enum pipe_format format)
{
switch (format) {
case PIPE_FORMAT_R16_FLOAT:
case PIPE_FORMAT_R32_FLOAT:
return TYPE_FLOAT;
case PIPE_FORMAT_R8_UINT:
case PIPE_FORMAT_R16_UINT:
case PIPE_FORMAT_R32_UINT:
case PIPE_FORMAT_R64_UINT:
return TYPE_UINT;
case PIPE_FORMAT_R8_SINT:
case PIPE_FORMAT_R16_SINT:
case PIPE_FORMAT_R32_SINT:
case PIPE_FORMAT_R64_SINT:
return TYPE_INT;
default:
assert(0);
return 0u;
}
}
static void
set_type(struct hash_table *types, void *key, ti_type type)
{
// convert nir_type
_mesa_hash_table_insert(types, key, (void *)type);
}
static ti_type
get_type(struct hash_table *types, void *key)
{
struct hash_entry *entry = _mesa_hash_table_search(types, key);
if (!entry)
return TYPE_NONE;
return (ti_type)(intptr_t)(entry->data);
}
static bool
update_instr_type(struct hash_table *types, nir_instr *instr, ti_type type)
{
if (instr->type == nir_instr_type_alu) {
nir_alu_instr *alu = nir_instr_as_alu(instr);
switch (alu->op) {
case nir_op_iadd:
case nir_op_isub:
case nir_op_ishl:
case nir_op_iand:
case nir_op_ior:
case nir_op_ixor:
set_type(types, &alu->def, type);
set_type(types, &alu->src[0].src, type);
set_type(types, &alu->src[1].src, type);
return true;
case nir_op_inot:
set_type(types, &alu->def, type);
set_type(types, &alu->src[0].src, type);
return true;
case nir_op_ieq:
case nir_op_ine:
set_type(types, &alu->src[0].src, type);
set_type(types, &alu->src[1].src, type);
return true;
case nir_op_bcsel:
set_type(types, &alu->def, type);
set_type(types, &alu->src[1].src, type);
set_type(types, &alu->src[2].src, type);
return true;
case nir_op_mov:
case nir_op_vec2:
case nir_op_vec3:
case nir_op_vec4:
set_type(types, &alu->def, type);
for (int i = 0; i < nir_op_infos[alu->op].num_inputs; i++)
set_type(types, &alu->src[i].src, type);
return true;
default:
return false;
}
} else if (instr->type == nir_instr_type_intrinsic) {
nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
nir_intrinsic_info info = nir_intrinsic_infos[intr->intrinsic];
switch (intr->intrinsic) {
case nir_intrinsic_load_reg:
set_type(types, &intr->def, type);
set_type(types, &intr->src[0], type);
return true;
case nir_intrinsic_store_reg:
set_type(types, &intr->src[0], type);
set_type(types, &intr->src[1], type);
return true;
case nir_intrinsic_decl_reg:
set_type(types, &intr->def, type);
return true;
case nir_intrinsic_load_global:
case nir_intrinsic_load_global_constant:
case nir_intrinsic_load_global_constant_bounded:
case nir_intrinsic_load_global_constant_offset:
case nir_intrinsic_load_push_constant:
set_type(types, &intr->def, type);
return true;
/* Scratch and shared are always UINT */
case nir_intrinsic_load_scratch:
case nir_intrinsic_store_scratch:
case nir_intrinsic_load_shared:
case nir_intrinsic_store_shared:
case nir_intrinsic_load_frag_coord:
return false;
case nir_intrinsic_store_global:
set_type(types, &intr->src[0], type);
return true;
case nir_intrinsic_read_first_invocation:
case nir_intrinsic_read_invocation:
case nir_intrinsic_quad_vote_all:
case nir_intrinsic_quad_vote_any:
case nir_intrinsic_quad_broadcast:
case nir_intrinsic_quad_swap_horizontal:
case nir_intrinsic_quad_swap_vertical:
case nir_intrinsic_quad_swap_diagonal:
case nir_intrinsic_rotate:
case nir_intrinsic_shuffle:
case nir_intrinsic_shuffle_down:
case nir_intrinsic_shuffle_up:
case nir_intrinsic_shuffle_xor:
set_type(types, &intr->src[0], type);
set_type(types, &intr->def, type);
return true;
default:
if (info.has_dest && info.num_srcs == 0) {
set_type(types, &intr->def, type);
return true;
}
return false;
}
} else
return false;
}
static void
infer_types_from_alu(struct hash_table *types, nir_alu_instr *alu)
{
// for most types, we infer the type from the nir_op_info,
// but some ALU instructions are the same for int and uint. Those
// have their sources and defs get marked by TYPE_GENERIC_INT.
switch (alu->op) {
case nir_op_iadd:
case nir_op_isub:
case nir_op_ishl:
// (N, N) -> N
set_type(types, &alu->def, TYPE_GENERIC_INT);
set_type(types, &alu->src[0].src, TYPE_GENERIC_INT);
set_type(types, &alu->src[1].src, TYPE_GENERIC_INT);
break;
case nir_op_iand:
case nir_op_ior:
case nir_op_ixor:
set_type(types, &alu->def, TYPE_GENERIC_INT_OR_BOOL);
set_type(types, &alu->src[0].src, TYPE_GENERIC_INT_OR_BOOL);
set_type(types, &alu->src[1].src, TYPE_GENERIC_INT_OR_BOOL);
break;
case nir_op_inot:
// N -> N
set_type(types, &alu->def, TYPE_GENERIC_INT_OR_BOOL);
set_type(types, &alu->src[0].src, TYPE_GENERIC_INT_OR_BOOL);
break;
case nir_op_ieq:
case nir_op_ine:
// (N, N) -> bool
set_type(types, &alu->def, TYPE_BOOL);
set_type(types, &alu->src[0].src, TYPE_GENERIC_INT_OR_BOOL);
set_type(types, &alu->src[1].src, TYPE_GENERIC_INT_OR_BOOL);
break;
case nir_op_bcsel:
// (bool, T, T) -> T
set_type(types, &alu->def, TYPE_GENERIC_DATA);
set_type(types, &alu->src[0].src, TYPE_BOOL);
set_type(types, &alu->src[1].src, TYPE_GENERIC_DATA);
set_type(types, &alu->src[2].src, TYPE_GENERIC_DATA);
break;
// These don't provide any type information, we rely on type propagation
// to fill in the type data
case nir_op_mov:
case nir_op_vec2:
case nir_op_vec3:
case nir_op_vec4:
break;
/* We don't have 32-bit width boolean, those are uints. */
case nir_op_b2b32:
set_type(types, &alu->def, TYPE_UINT);
set_type(types, &alu->src[0].src, TYPE_UINT);
break;
default: {
// set type for def
const nir_op_info *info = &nir_op_infos[alu->op];
set_type(types, &alu->def, ti_type_from_nir(info->output_type));
for (int i = 0; i < info->num_inputs; i++) {
// set type for src
set_type(types, &alu->src[i].src,
ti_type_from_nir(info->input_types[i]));
}
}
}
}
static void
infer_types_from_intrinsic(struct hash_table *types, nir_intrinsic_instr *instr)
{
switch (instr->intrinsic) {
case nir_intrinsic_load_input:
case nir_intrinsic_load_interpolated_input:
case nir_intrinsic_load_output: {
ti_type ty = ti_type_from_nir(nir_intrinsic_dest_type(instr));
set_type(types, &instr->def, ty);
break;
}
case nir_intrinsic_load_sample_pos_from_id:
set_type(types, &instr->def, TYPE_FLOAT);
set_type(types, &instr->src[0], TYPE_UINT);
break;
case nir_intrinsic_load_global_constant:
set_type(types, &instr->def, TYPE_GENERIC_DATA);
set_type(types, &instr->src[0], TYPE_UINT);
break;
case nir_intrinsic_load_global_constant_bounded:
set_type(types, &instr->def, TYPE_GENERIC_DATA);
set_type(types, &instr->src[0], TYPE_UINT);
set_type(types, &instr->src[1], TYPE_UINT);
set_type(types, &instr->src[2], TYPE_UINT);
break;
case nir_intrinsic_load_global_constant_offset:
set_type(types, &instr->def, TYPE_GENERIC_DATA);
set_type(types, &instr->src[0], TYPE_UINT);
set_type(types, &instr->src[1], TYPE_UINT);
break;
case nir_intrinsic_load_global:
case nir_intrinsic_load_push_constant:
set_type(types, &instr->def, TYPE_GENERIC_DATA);
set_type(types, &instr->src[0], TYPE_UINT);
break;
case nir_intrinsic_global_atomic:
case nir_intrinsic_global_atomic_swap:
case nir_intrinsic_shared_atomic:
case nir_intrinsic_shared_atomic_swap: {
ti_type type =
ti_type_from_nir(nir_atomic_op_type(nir_intrinsic_atomic_op(instr)));
set_type(types, &instr->def, type);
set_type(types, &instr->src[0], TYPE_UINT);
set_type(types, &instr->src[1], type);
set_type(types, &instr->src[2], type);
break;
}
case nir_intrinsic_store_global:
set_type(types, &instr->src[0], TYPE_GENERIC_DATA);
set_type(types, &instr->src[1], TYPE_UINT);
break;
case nir_intrinsic_store_output: {
ti_type ty = ti_type_from_nir(nir_intrinsic_src_type(instr));
set_type(types, &instr->src[0], ty);
break;
}
case nir_intrinsic_decl_reg:
if (nir_intrinsic_bit_size(instr) == 1)
set_type(types, &instr->def, TYPE_BOOL);
else
set_type(types, &instr->def, TYPE_NONE);
break;
case nir_intrinsic_store_reg:
set_type(types, &instr->src[0], TYPE_NONE);
set_type(types, &instr->src[1], TYPE_NONE);
break;
case nir_intrinsic_load_reg:
set_type(types, &instr->src[0], TYPE_NONE);
set_type(types, &instr->def, TYPE_NONE);
break;
case nir_intrinsic_load_scratch:
case nir_intrinsic_load_shared:
set_type(types, &instr->def, TYPE_UINT);
set_type(types, &instr->src[0], TYPE_UINT);
break;
case nir_intrinsic_store_scratch:
case nir_intrinsic_store_shared:
set_type(types, &instr->src[0], TYPE_UINT);
set_type(types, &instr->src[1], TYPE_UINT);
break;
case nir_intrinsic_load_first_vertex:
case nir_intrinsic_load_base_instance:
case nir_intrinsic_load_vertex_id:
case nir_intrinsic_load_instance_id:
case nir_intrinsic_load_primitive_id:
case nir_intrinsic_load_layer_id:
case nir_intrinsic_load_workgroup_id:
case nir_intrinsic_load_subgroup_id:
case nir_intrinsic_load_local_invocation_id:
case nir_intrinsic_load_global_invocation_id:
case nir_intrinsic_load_local_invocation_index:
case nir_intrinsic_load_num_workgroups:
case nir_intrinsic_load_num_subgroups:
case nir_intrinsic_load_subgroup_size:
case nir_intrinsic_load_sample_id:
case nir_intrinsic_load_sample_mask:
case nir_intrinsic_load_subgroup_invocation:
case nir_intrinsic_load_amplification_id_kk:
set_type(types, &instr->def, TYPE_UINT);
break;
case nir_intrinsic_load_vulkan_descriptor:
case nir_intrinsic_load_barycentric_at_sample:
set_type(types, &instr->src[0], TYPE_UINT);
set_type(types, &instr->def, TYPE_UINT);
break;
case nir_intrinsic_load_buffer_ptr_kk:
set_type(types, &instr->def, TYPE_UINT);
break;
// The defs of these instructions don't participate in type inference
// but their sources are pointers (i.e. uints).
case nir_intrinsic_load_texture_handle_kk:
case nir_intrinsic_load_depth_texture_kk:
set_type(types, &instr->src[0], TYPE_UINT);
break;
case nir_intrinsic_load_sampler_handle_kk:
set_type(types, &instr->def, TYPE_SAMPLER);
break;
case nir_intrinsic_store_clip_distance_kk:
set_type(types, &instr->src[0], TYPE_FLOAT);
break;
case nir_intrinsic_ddx:
case nir_intrinsic_ddy:
case nir_intrinsic_ddx_coarse:
case nir_intrinsic_ddy_coarse:
case nir_intrinsic_ddx_fine:
case nir_intrinsic_ddy_fine:
case nir_intrinsic_load_barycentric_at_offset:
set_type(types, &instr->src[0], TYPE_FLOAT);
set_type(types, &instr->def, TYPE_FLOAT);
break;
case nir_intrinsic_load_point_coord:
case nir_intrinsic_load_sample_pos:
case nir_intrinsic_load_frag_coord:
set_type(types, &instr->def, TYPE_FLOAT);
break;
case nir_intrinsic_load_front_face:
case nir_intrinsic_elect:
case nir_intrinsic_load_helper_invocation:
case nir_intrinsic_is_helper_invocation:
set_type(types, &instr->def, TYPE_BOOL);
break;
case nir_intrinsic_load_constant_agx:
set_type(types, &instr->src[0], TYPE_UINT);
set_type(types, &instr->src[1], TYPE_UINT);
set_type(types, &instr->def,
ti_type_from_pipe_format(nir_intrinsic_format(instr)));
break;
case nir_intrinsic_bindless_image_load:
set_type(types, &instr->def,
ti_type_from_nir(nir_intrinsic_dest_type(instr)));
set_type(types, &instr->src[1], TYPE_UINT); // coords
set_type(types, &instr->src[3], TYPE_UINT); // level
break;
case nir_intrinsic_bindless_image_store:
set_type(types, &instr->src[1], TYPE_UINT); // coords
set_type(types, &instr->src[3],
ti_type_from_nir(nir_intrinsic_src_type(instr)));
set_type(types, &instr->src[4], TYPE_UINT); // level
break;
case nir_intrinsic_demote_if:
case nir_intrinsic_terminate_if:
set_type(types, &instr->src[0], TYPE_BOOL);
break;
case nir_intrinsic_bindless_image_atomic:
case nir_intrinsic_bindless_image_atomic_swap: {
set_type(types, &instr->src[1], TYPE_UINT); // coords
set_type(types, &instr->src[2], TYPE_UINT); // level
ti_type type =
ti_type_from_nir(nir_atomic_op_type(nir_intrinsic_atomic_op(instr)));
set_type(types, &instr->src[3], type);
if (instr->intrinsic == nir_intrinsic_bindless_image_atomic_swap)
set_type(types, &instr->src[4], type);
set_type(types, &instr->def, type);
break;
}
case nir_intrinsic_ballot:
set_type(types, &instr->src[0], TYPE_BOOL);
set_type(types, &instr->def, TYPE_UINT);
break;
case nir_intrinsic_vote_all:
case nir_intrinsic_vote_any:
set_type(types, &instr->src[0], TYPE_BOOL);
set_type(types, &instr->def, TYPE_BOOL);
break;
case nir_intrinsic_read_first_invocation:
case nir_intrinsic_quad_swap_horizontal:
case nir_intrinsic_quad_swap_vertical:
case nir_intrinsic_quad_swap_diagonal:
set_type(types, &instr->src[0], TYPE_GENERIC_DATA);
set_type(types, &instr->def, TYPE_GENERIC_DATA);
break;
case nir_intrinsic_read_invocation:
case nir_intrinsic_quad_broadcast:
case nir_intrinsic_rotate:
case nir_intrinsic_shuffle:
case nir_intrinsic_shuffle_down:
case nir_intrinsic_shuffle_up:
case nir_intrinsic_shuffle_xor:
set_type(types, &instr->src[0], TYPE_GENERIC_DATA);
set_type(types, &instr->def, TYPE_GENERIC_DATA);
set_type(types, &instr->src[1], TYPE_UINT);
break;
case nir_intrinsic_reduce:
switch (nir_intrinsic_reduction_op(instr)) {
case nir_op_iand:
case nir_op_ior:
case nir_op_ixor:
case nir_op_iadd:
case nir_op_imul:
set_type(types, &instr->src[0], TYPE_GENERIC_INT);
set_type(types, &instr->def, TYPE_GENERIC_INT);
break;
case nir_op_imax:
case nir_op_imin:
set_type(types, &instr->src[0], TYPE_INT);
set_type(types, &instr->def, TYPE_INT);
break;
case nir_op_umax:
case nir_op_umin:
set_type(types, &instr->src[0], TYPE_UINT);
set_type(types, &instr->def, TYPE_UINT);
break;
case nir_op_fadd:
case nir_op_fmax:
case nir_op_fmin:
case nir_op_fmul:
set_type(types, &instr->src[0], TYPE_FLOAT);
set_type(types, &instr->def, TYPE_FLOAT);
break;
default:
break;
}
break;
default:
break;
}
}
static void
infer_types_from_tex(struct hash_table *types, nir_tex_instr *tex)
{
set_type(types, &tex->def, ti_type_from_nir(tex->dest_type));
for (int i = 0; i < tex->num_srcs; i++) {
nir_src *src = &tex->src[i].src;
switch (tex->src[i].src_type) {
case nir_tex_src_coord:
if (tex->op == nir_texop_txf || tex->op == nir_texop_txf_ms)
set_type(types, src, TYPE_UINT);
else
set_type(types, src, TYPE_FLOAT);
break;
case nir_tex_src_comparator:
set_type(types, src, TYPE_FLOAT);
break;
case nir_tex_src_offset:
set_type(types, src, TYPE_INT);
break;
case nir_tex_src_bias:
set_type(types, src, TYPE_FLOAT);
break;
case nir_tex_src_lod:
if (tex->op == nir_texop_txf || tex->op == nir_texop_txf_ms ||
tex->op == nir_texop_txs)
set_type(types, src, TYPE_UINT);
else
set_type(types, src, TYPE_FLOAT);
break;
case nir_tex_src_min_lod:
set_type(types, src, TYPE_FLOAT);
break;
case nir_tex_src_ms_index:
set_type(types, src, TYPE_UINT);
break;
case nir_tex_src_ddx:
case nir_tex_src_ddy:
set_type(types, src, TYPE_FLOAT);
break;
default:
break;
}
}
}
static void
infer_types_from_instr(struct hash_table *types, nir_instr *instr)
{
switch (instr->type) {
case nir_instr_type_alu:
infer_types_from_alu(types, nir_instr_as_alu(instr));
return;
case nir_instr_type_intrinsic:
infer_types_from_intrinsic(types, nir_instr_as_intrinsic(instr));
return;
case nir_instr_type_tex:
infer_types_from_tex(types, nir_instr_as_tex(instr));
break;
default:
break;
}
}
static bool
propagate_types(struct hash_table *types, nir_instr *instr)
{
bool progress = false;
switch (instr->type) {
case nir_instr_type_alu: {
nir_alu_instr *alu = nir_instr_as_alu(instr);
nir_op_info info = nir_op_infos[alu->op];
for (int i = 0; i < info.num_inputs; i++) {
ti_type src_type = get_type(types, &alu->src[i].src);
ti_type def_type = get_type(types, alu->src[i].src.ssa);
ti_type unified_type = unify_types(src_type, def_type);
nir_instr *parent_instr = nir_def_instr(alu->src[i].src.ssa);
if (unified_type > src_type) {
progress |= update_instr_type(types, instr, unified_type);
} else if (unified_type > def_type) {
progress |= update_instr_type(types, parent_instr, unified_type);
}
}
break;
}
case nir_instr_type_intrinsic: {
nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
nir_intrinsic_info info = nir_intrinsic_infos[intr->intrinsic];
for (int i = 0; i < info.num_srcs; i++) {
ti_type src_type = get_type(types, &intr->src[i]);
ti_type def_type = get_type(types, intr->src[i].ssa);
ti_type unified_type = unify_types(src_type, def_type);
nir_instr *parent_instr = nir_def_instr(intr->src[i].ssa);
if (unified_type > src_type) {
progress |= update_instr_type(types, instr, unified_type);
} else if (unified_type > def_type) {
progress |= update_instr_type(types, parent_instr, unified_type);
}
}
break;
}
case nir_instr_type_tex: {
nir_tex_instr *tex = nir_instr_as_tex(instr);
for (int i = 0; i < tex->num_srcs; i++) {
ti_type src_type = get_type(types, &tex->src[i].src);
ti_type def_type = get_type(types, tex->src[i].src.ssa);
ti_type unified_type = unify_types(src_type, def_type);
if (src_type == 0)
continue;
nir_instr *parent_instr = nir_def_instr(tex->src[i].src.ssa);
if (unified_type > def_type) {
progress |= update_instr_type(types, parent_instr, unified_type);
}
}
break;
}
default:
break;
}
return progress;
}
static const char *float_names[] = {"float", "float2", "float3", "float4"};
static const char *half_names[] = {"half", "half2", "half3", "half4"};
static const char *bool_names[] = {"bool", "bool2", "bool3", "bool4"};
static const char *int8_names[] = {"char", "char2", "char3", "char4"};
static const char *uint8_names[] = {"uchar", "uchar2", "uchar3", "uchar4"};
static const char *int16_names[] = {"short", "short2", "short3", "short4"};
static const char *uint16_names[] = {"ushort", "ushort2", "ushort3", "ushort4"};
static const char *int32_names[] = {"int", "int2", "int3", "int4"};
static const char *uint32_names[] = {"uint", "uint2", "uint3", "uint4"};
static const char *int64_names[] = {"long", "long2", "long3", "long4"};
static const char *uint64_names[] = {"ulong", "ulong2", "ulong3", "ulong4"};
static const char *
ti_type_to_msl_type(ti_type type, uint8_t bit_width, uint8_t num_components)
{
switch (type) {
case TYPE_GENERIC_DATA:
case TYPE_GENERIC_INT:
case TYPE_GENERIC_INT_OR_BOOL:
case TYPE_UINT:
switch (bit_width) {
case 1:
return bool_names[num_components - 1];
case 8:
return uint8_names[num_components - 1];
case 16:
return uint16_names[num_components - 1];
case 32:
return uint32_names[num_components - 1];
case 64:
return uint64_names[num_components - 1];
default:
assert(!"Bad uint length");
}
break;
case TYPE_BOOL:
return bool_names[num_components - 1];
case TYPE_INT:
switch (bit_width) {
case 8:
return int8_names[num_components - 1];
case 16:
return int16_names[num_components - 1];
case 32:
return int32_names[num_components - 1];
case 64:
return int64_names[num_components - 1];
default:
assert(!"Bad uint length");
}
break;
case TYPE_FLOAT:
switch (bit_width) {
case 16:
return half_names[num_components - 1];
case 32:
return float_names[num_components - 1];
default:
assert(!"Bad float length");
}
break;
case TYPE_SAMPLER:
return "sampler";
default:
return NULL;
}
return NULL;
}
const char *
msl_uint_type(uint8_t bit_size, uint8_t num_components)
{
return ti_type_to_msl_type(TYPE_UINT, bit_size, num_components);
}
const char *
msl_type_for_def(struct hash_table *types, nir_def *def)
{
ti_type type = get_type(types, def);
return ti_type_to_msl_type(type, def->bit_size, def->num_components);
}
const char *
msl_type_for_src(struct hash_table *types, nir_src *src)
{
ti_type type = get_type(types, src);
// This won't necessarily work for alu srcs but for intrinsics it's fine.
return ti_type_to_msl_type(type, src->ssa->bit_size,
src->ssa->num_components);
}
const char *
msl_bitcast_for_src(struct hash_table *types, nir_src *src)
{
ti_type src_type = get_type(types, src);
ti_type def_type = get_type(types, src->ssa);
if (nir_src_is_if(src))
return NULL;
if (src_type != def_type) {
/* bool types cannot use as_type casting */
if (src_type == TYPE_BOOL || def_type == TYPE_BOOL)
return NULL;
// produce bitcast _into_ src_type
return ti_type_to_msl_type(src_type, src->ssa->bit_size,
src->ssa->num_components);
} else {
return NULL;
}
}
static void
emit_src_component(struct nir_to_msl_ctx *ctx, nir_src *src, unsigned comp)
{
ti_type type = get_type(ctx->types, src);
switch (type) {
case TYPE_FLOAT: {
double v = nir_src_comp_as_float(*src, comp);
if (v == INFINITY) {
P(ctx, "(INFINITY");
} else if (v == -INFINITY) {
P(ctx, "(-INFINITY");
} else if (isnan(v)) {
P(ctx, "(NAN");
} else {
/* Building the types explicitly is required since the MSL compiler is
* too dumb to understand that "max(as_type<int>(t53), -2147483648)" is
* not ambiguous since both are ints and there's no room for longs.
* From CTS test:
* dEQP-VK.renderpass.suballocation.multisample.r32_sint.samples_2 */
if (src->ssa->bit_size == 16) {
P(ctx, "half(");
} else {
P(ctx, "float(");
}
P(ctx, "%.*le", DBL_DECIMAL_DIG, nir_src_comp_as_float(*src, comp));
}
break;
}
case TYPE_BOOL:
P(ctx, "bool(%d", nir_src_comp_as_bool(*src, comp));
break;
case TYPE_INT:
switch (src->ssa->bit_size) {
case 8:
P(ctx, "char(");
break;
case 16:
P(ctx, "short(");
break;
case 32:
P(ctx, "int(");
break;
case 64:
P(ctx, "long(");
break;
default:
UNREACHABLE("Incorrect bit_size for TYPE_INT");
}
P(ctx, "%" PRId64, nir_src_comp_as_int(*src, comp));
break;
case TYPE_UINT:
case TYPE_GENERIC_DATA:
case TYPE_GENERIC_INT:
case TYPE_GENERIC_INT_OR_BOOL:
switch (src->ssa->bit_size) {
case 1:
P(ctx, "bool(");
break;
case 8:
P(ctx, "uchar(");
break;
case 16:
P(ctx, "ushort(");
break;
case 32:
P(ctx, "uint(");
break;
case 64:
P(ctx, "ulong(");
break;
default:
UNREACHABLE("Incorrect bit_size for TYPE_UINT");
}
P(ctx, "%" PRIu64 "u", nir_src_comp_as_uint(*src, comp));
break;
case TYPE_NONE:
assert(0);
P(ctx, "UNTYPED!");
break;
default:
return;
}
P(ctx, ")");
}
void
msl_src_as_const(struct nir_to_msl_ctx *ctx, nir_src *src)
{
ti_type type = get_type(ctx->types, src);
if (src->ssa->num_components == 1) {
emit_src_component(ctx, src, 0);
} else {
P(ctx, "%s(",
ti_type_to_msl_type(type, src->ssa->bit_size,
src->ssa->num_components));
for (int i = 0; i < src->ssa->num_components; i++) {
if (i)
P(ctx, ", ");
emit_src_component(ctx, src, i);
}
P(ctx, ")");
}
}
struct hash_table *
msl_infer_types(nir_shader *shader)
{
struct hash_table *types = _mesa_pointer_hash_table_create(NULL);
bool progress = false;
// First, seed the types for every instruction for every source and def
nir_foreach_function_impl(impl, shader) {
nir_foreach_block(block, impl) {
nir_foreach_instr(instr, block) {
infer_types_from_instr(types, instr);
}
}
}
do {
progress = false;
nir_foreach_function_impl(impl, shader) {
nir_foreach_block(block, impl) {
nir_foreach_instr(instr, block) {
progress |= propagate_types(types, instr);
}
}
}
} while (progress);
return types;
}
bool
msl_src_is_float(struct nir_to_msl_ctx *ctx, nir_src *src)
{
return get_type(ctx->types, src) == TYPE_FLOAT;
}
bool
msl_def_is_sampler(struct nir_to_msl_ctx *ctx, nir_def *def)
{
return get_type(ctx->types, def) == TYPE_SAMPLER;
}
File diff suppressed because it is too large. Load diff
+121
View File
@@ -0,0 +1,121 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "nir.h"
enum pipe_format;
struct nir_to_msl_options {
void *mem_ctx;
uint64_t disabled_workarounds;
/* Required to correctly declare fragment outputs. Shader may contain
* shrinked writes which can lead to writting less components than the ones
* the render target has. This leads to an incorrect calculation of the
* component count for the render target formats. */
uint8_t rts_component_count[MAX_DRAW_BUFFERS];
};
/* Assumes nir_shader_gather_info has been called beforehand. */
char *nir_to_msl(nir_shader *shader, struct nir_to_msl_options *options);
/* Call this after all API-specific lowerings. It will bring the NIR out of SSA
* at the end */
bool msl_optimize_nir(struct nir_shader *nir);
/* Call this before all API-speicific lowerings, it will */
void msl_preprocess_nir(struct nir_shader *nir);
/* Call this before all API-specific lowerings. It will pre-process with
* instruction workarounds based on the disabled workarounds bitmask. */
void msl_preprocess_nir_workarounds(struct nir_shader *nir,
uint64_t disabled_workarounds);
enum msl_tex_access_flag {
MSL_ACCESS_SAMPLE = 0,
MSL_ACCESS_READ,
MSL_ACCESS_WRITE,
MSL_ACCESS_READ_WRITE,
};
static inline enum msl_tex_access_flag
msl_convert_access_flag(enum gl_access_qualifier qual)
{
enum gl_access_qualifier readwrite =
(ACCESS_NON_WRITEABLE | ACCESS_NON_READABLE);
if ((qual & readwrite) == readwrite)
return MSL_ACCESS_READ_WRITE;
if (qual & ACCESS_NON_WRITEABLE)
return MSL_ACCESS_READ;
if (qual & ACCESS_NON_READABLE)
return MSL_ACCESS_WRITE;
return MSL_ACCESS_READ_WRITE;
}
bool msl_nir_fs_force_output_signedness(
nir_shader *nir, enum pipe_format render_target_formats[MAX_DRAW_BUFFERS]);
bool msl_nir_vs_remove_point_size_write(nir_builder *b,
nir_intrinsic_instr *intrin,
void *data);
bool msl_nir_fs_remove_depth_write(nir_builder *b, nir_intrinsic_instr *intrin,
void *data);
bool msl_lower_textures(nir_shader *s);
bool msl_lower_static_sample_mask(nir_shader *nir, uint32_t sample_mask);
bool msl_ensure_depth_write(nir_shader *nir);
bool msl_ensure_vertex_position_output(nir_shader *nir);
bool msl_ensure_vertex_point_size_output(nir_shader *nir);
bool msl_nir_fs_io_types(nir_shader *nir);
bool msl_nir_vs_io_types(nir_shader *nir);
bool msl_nir_fake_guard_for_discards(struct nir_shader *nir);
bool msl_nir_lower_sample_shading(nir_shader *nir);
void msl_nir_lower_clip_cull_distance(nir_shader *nir,
unsigned num_cull_distances);
bool msl_nir_lower_instance_id(nir_shader *nir);
bool msl_gather_uses_per_draw_data(nir_shader *nir);
static const nir_shader_compiler_options kk_nir_options = {
.lower_fdph = true,
.has_fsub = true,
.has_isub = true,
.float_mul_add16 = nir_float_muladd_support_has_ffma,
.float_mul_add32 = nir_float_muladd_support_has_ffma,
.lower_extract_word = true,
.lower_extract_byte = true,
.lower_insert_word = true,
.lower_insert_byte = true,
.lower_fmod = true,
.discard_is_demote = true,
.lower_device_index_to_zero = true,
.lower_pack_64_2x32_split = true,
.lower_unpack_64_2x32_split = true,
.lower_pack_64_2x32 = true,
.lower_pack_64_4x16 = true,
.lower_pack_half_2x16 = true,
.lower_pack_split = true,
.lower_unpack_half_2x16 = true,
.has_cs_global_id = true,
.lower_fquantize2f16 = true,
.lower_scmp = true,
.lower_ifind_msb = true,
.lower_ufind_msb = true,
.lower_find_lsb = true,
.has_uclz = true,
.lower_mul_2x32_64 = true,
.lower_uadd_carry = true,
.lower_usub_borrow = true,
.compact_arrays = true,
/* Metal does not support double. */
.lower_doubles_options = (nir_lower_doubles_options)(~0),
.lower_int64_options = nir_lower_ufind_msb64 | nir_lower_subgroup_shuffle64,
.io_options = nir_io_mediump_is_32bit,
};
+198
View File
@@ -0,0 +1,198 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include "compiler/nir_to_msl.h"
#include "spirv/nir_spirv.h"
static int
load_spirv(const char *filename, uint32_t **words, size_t *nwords)
{
const size_t CHUNK_SIZE = 4096;
uint32_t buf[CHUNK_SIZE];
FILE *input = fopen(filename, "r");
if (!input) {
fprintf(stderr, "Could not open file %s: %s\n", filename,
strerror(errno));
return -1;
}
*nwords = 0;
*words = malloc(CHUNK_SIZE * sizeof(buf[0]));
size_t read_size;
while (1) {
read_size = fread(buf, sizeof(buf[0]), CHUNK_SIZE, input);
if (read_size == 0)
break;
*words = realloc(*words, (*nwords + read_size) * sizeof(buf[0]));
memcpy(*words + *nwords, buf, sizeof(buf[0]) * read_size);
*nwords += read_size;
};
if (*words[0] != 0x07230203) {
fprintf(stderr, "%s is not a SPIR-V file?\n", filename);
return -1;
}
return 0;
}
static void
debug_callback(void *priv, enum nir_spirv_debug_level debuglevel, size_t offset,
const char *message)
{
fprintf(stderr, "<%d> at %zu %s\n", debuglevel, offset, message);
}
static int
type_size_vec4(const struct glsl_type *type, bool bindless)
{
return glsl_count_attribute_slots(type, false);
}
static void
shared_var_info(const struct glsl_type *type, unsigned *size, unsigned *align)
{
assert(glsl_type_is_vector_or_scalar(type));
uint32_t comp_size =
glsl_type_is_boolean(type) ? 4 : glsl_get_bit_size(type) / 8;
unsigned length = glsl_get_vector_elements(type);
*size = comp_size * length, *align = comp_size;
}
static void
optimize(nir_shader *nir)
{
msl_preprocess_nir(nir);
msl_preprocess_nir_workarounds(nir, 0);
nir_lower_compute_system_values_options csv_options = {
.has_base_global_invocation_id = 0,
.has_base_workgroup_id = true,
};
NIR_PASS(_, nir, nir_lower_compute_system_values, &csv_options);
NIR_PASS(_, nir, nir_lower_explicit_io, nir_var_mem_push_const,
nir_address_format_32bit_offset);
NIR_PASS(_, nir, nir_lower_explicit_io,
nir_var_mem_global | nir_var_mem_ubo | nir_var_mem_ssbo,
nir_address_format_64bit_global);
if (nir->info.stage == MESA_SHADER_COMPUTE) {
if (!nir->info.shared_memory_explicit_layout) {
/* There may be garbage in shared_size, but it's the job of
* nir_lower_vars_to_explicit_types to allocate it. We have to reset to
* avoid overallocation.
*/
nir->info.shared_size = 0;
NIR_PASS(_, nir, nir_lower_vars_to_explicit_types, nir_var_mem_shared,
shared_var_info);
}
NIR_PASS(_, nir, nir_lower_explicit_io, nir_var_mem_shared,
nir_address_format_32bit_offset);
}
NIR_PASS(_, nir, nir_lower_io, nir_var_shader_in | nir_var_shader_out,
type_size_vec4, (nir_lower_io_options)0);
NIR_PASS(_, nir, nir_lower_variable_initializers, ~nir_var_function_temp);
NIR_PASS(_, nir, nir_remove_dead_variables,
nir_var_shader_in | nir_var_shader_out | nir_var_system_value,
NULL);
NIR_PASS(_, nir, nir_lower_io_vars_to_temporaries,
nir_shader_get_entrypoint(nir), nir_var_shader_out);
nir_lower_compute_system_values_options options = {
.has_base_global_invocation_id = 0,
};
NIR_PASS(_, nir, nir_lower_system_values);
NIR_PASS(_, nir, nir_lower_compute_system_values, &options);
NIR_PASS(_, nir, nir_lower_global_vars_to_local);
NIR_PASS(_, nir, nir_lower_load_const_to_scalar);
msl_optimize_nir(nir);
}
static mesa_shader_stage
stage_from_filename(const char *filename)
{
struct StageMapping {
char *name;
mesa_shader_stage stage;
};
struct StageMapping stage_mappings[] = {
{.name = ".frag.", .stage = MESA_SHADER_FRAGMENT},
{.name = ".vert.", .stage = MESA_SHADER_VERTEX},
{.name = ".comp.", .stage = MESA_SHADER_COMPUTE},
};
for (int i = 0; i < ARRAY_SIZE(stage_mappings); i++) {
if (strstr(filename, stage_mappings[i].name))
return stage_mappings[i].stage;
}
return MESA_SHADER_NONE;
}
int
main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "Usage: kosmicomp filename.spv\n");
return 1;
}
// read file
size_t nwords = 0;
uint32_t *words = NULL;
int result = load_spirv(argv[1], &words, &nwords);
if (result == -1) {
return 2;
}
// run spirv_to_nir
struct spirv_to_nir_options options = {
.environment = NIR_SPIRV_VULKAN,
.debug =
{
.func = &debug_callback,
.private_data = NULL,
},
.ubo_addr_format = nir_address_format_64bit_global,
.ssbo_addr_format = nir_address_format_64bit_global,
.phys_ssbo_addr_format = nir_address_format_64bit_global,
};
glsl_type_singleton_init_or_ref();
struct nir_shader_compiler_options nir_options = {
.lower_fdph = 1,
};
mesa_shader_stage stage = stage_from_filename(argv[1]);
if (stage == MESA_SHADER_NONE) {
fprintf(stderr, "Couldn't guess shader stage from %s\n", argv[1]);
return 4;
}
nir_shader *shader =
spirv_to_nir(words, nwords, NULL, stage, "main", &options, &nir_options);
if (!shader) {
fprintf(stderr, "Compilation failed!\n");
return 3;
}
// print nir
nir_print_shader(shader, stdout);
optimize(shader);
nir_print_shader(shader, stdout);
struct nir_to_msl_options translate_options = {
.mem_ctx = shader,
.disabled_workarounds = 0u,
};
char *msl_text = nir_to_msl(shader, &translate_options);
fputs(msl_text, stdout);
ralloc_free(msl_text);
return 0;
}
+90
View File
@@ -0,0 +1,90 @@
/*
* Copyright 2026 LunarG, Inc.
* Copyright 2026 Google LLC
* Copyright 2024 Valve Corporation
* SPDX-License-Identifier: MIT
*/
#include "compiler/libcl/libcl_vk.h"
#include "poly/cl/restart.h"
/*
* To implement drawIndirectCount generically, we dispatch a kernel to
* clone-and-patch the indirect buffer, predicating out draws as appropriate.
*/
static uint
libkk_predicate_draw(global uint32_t *out, constant uint32_t *in,
uint32_t out_stride_el, uint32_t in_stride_el,
uint draw_id, bool enabled)
{
out += draw_id * out_stride_el;
in += draw_id * in_stride_el;
/* Copy enabled draws, zero predicated draws. */
for (uint i = 0; i < out_stride_el; ++i) {
out[i] = enabled ? in[i] : 0;
}
}
/*
* Indirect Draw predicate: value > draw_id
*/
KERNEL(32)
libkk_predicate_indirect_gt_draw_id(global uint32_t *out,
constant uint32_t *in,
constant uint32_t *value,
uint32_t out_stride_el,
uint32_t in_stride_el)
{
uint draw_id = cl_global_id.x;
bool enabled = *value > draw_id;
libkk_predicate_draw(out, in, out_stride_el, in_stride_el, draw_id, enabled);
}
/*
* Indirect Draw predicate: value == 0
*/
KERNEL(32)
libkk_predicate_indirect_eq_zero(global uint32_t *out, constant uint32_t *in,
constant uint32_t *value,
uint32_t out_stride_el, uint32_t in_stride_el)
{
uint draw_id = cl_global_id.x;
bool enabled = *value == 0;
libkk_predicate_draw(out, in, out_stride_el, in_stride_el, draw_id, enabled);
}
/*
* Indirect Draw predicate: value != 0
*/
KERNEL(32)
libkk_predicate_indirect_neq_zero(global uint32_t *out, constant uint32_t *in,
constant uint32_t *value,
uint32_t out_stride_el,
uint32_t in_stride_el)
{
uint draw_id = cl_global_id.x;
bool enabled = *value != 0;
libkk_predicate_draw(out, in, out_stride_el, in_stride_el, draw_id, enabled);
}
KERNEL(1024)
libkk_unroll_geometry(
uint64_t index_buffer, global struct poly_heap *heap,
constant uint32_t *in_draw, global uint32_t *out_draw,
uint32_t in_draw_stride_el, uint32_t restart_index,
uint32_t index_buffer_size_el, uint32_t in_el_size_B,
uint32_t out_el_size_B, uint32_t flatshade_first, uint32_t mode)
{
uint gid = cl_group_id.x;
in_draw += gid * in_draw_stride_el;
out_draw += gid * 5;
POLY_DECL_UNROLL_RESTART_SCRATCH(scratch, 1024);
poly_unroll_geometry(out_draw, heap, in_draw, index_buffer,
index_buffer_size_el, in_el_size_B, out_el_size_B,
restart_index, flatshade_first, mode, scratch);
}
+43
View File
@@ -0,0 +1,43 @@
/*
* Copyright 2026 LunarG, Inc.
* Copyright 2026 Google LLC
* Copyright 2023 Alyssa Rosenzweig
* Copyright 2023 Valve Corporation
* SPDX-License-Identifier: MIT
*/
#include "compiler/libcl/libcl_vk.h"
#include "poly/geometry.h"
#include "poly/tessellator.h"
KERNEL(1)
libkk_prefix_sum_tess(global struct poly_tess_params *p)
{
if (cl_local_id.x != 0)
return;
/* The last element of an inclusive prefix sum is the total sum */
uint total = 0;
if (p->nr_patches > 0) {
for (uint32_t i = 0u; i < p->nr_patches; ++i) {
total += p->counts[i];
p->counts[i] = total;
}
}
/* Allocate 4-byte indices */
uint32_t elsize_B = sizeof(uint32_t);
uint32_t size_B = total * elsize_B;
uint alloc_B = poly_heap_alloc_offs(p->heap, size_B);
p->index_buffer = (global uint32_t *)(((uintptr_t)p->heap->base) + alloc_B);
/* ...and now we can generate the API indexed draw */
global uint32_t *desc = p->out_draws;
desc[0] = total; /* count */
desc[1] = 1; /* instance_count */
desc[2] = alloc_B / elsize_B; /* start */
desc[3] = 0; /* index_bias */
desc[4] = 0; /* start_instance */
}
+90
View File
@@ -0,0 +1,90 @@
/*
* Copyright 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2024 Alyssa Rosenzweig
* Copyright 2024 Valve Corporation
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "compiler/libcl/libcl_vk.h"
#include "kk_query.h"
static inline global uint64_t *
query_report(global uint64_t *results, global uint16_t *oq_index,
uint reports_per_query, uint query)
{
/* For occlusion queries, results[] points to the device global heap. We
* need to remap indices according to the query pool's allocation.
*/
uint result_index = oq_index ? oq_index[query] : query;
return results + (result_index * reports_per_query);
}
/**
* Goes through a series of consecutive query indices in the given pool,
* setting all element values to 0 and emitting them as available.
*/
KERNEL(1)
libkk_reset_query(global uint32_t *availability, global uint64_t *results,
global uint16_t *oq_index, uint32_t first_query,
uint16_t reports_per_query, int set_available)
{
uint32_t query = first_query + cl_global_id.x;
uint64_t value = 0;
if (availability) {
availability[query] = set_available;
} else {
value = set_available ? 0 : UINT64_MAX;
}
global uint64_t *report =
query_report(results, oq_index, reports_per_query, query);
/* XXX: is this supposed to happen on the begin? */
for (unsigned j = 0; j < reports_per_query; ++j) {
report[j] = value;
}
}
KERNEL(1)
libkk_write_u32_array(global struct libkk_imm_write *write_array)
{
uint id = cl_global_id.x;
*(write_array[id].address) = write_array[id].value;
}
KERNEL(1)
libkk_copy_queries(global uint32_t *availability, global uint64_t *results,
global uint16_t *oq_index, uint64_t dst_addr,
uint64_t dst_stride, uint32_t first_query,
VkQueryResultFlagBits flags, uint16_t reports_per_query)
{
uint index = cl_group_id.x;
uint64_t dst = dst_addr + (((uint64_t)index) * dst_stride);
uint32_t query = first_query + index;
bool available;
if (availability)
available = availability[query];
else
available = (results[query] != LIBKK_QUERY_UNAVAILABLE);
if (available || (flags & VK_QUERY_RESULT_PARTIAL_BIT)) {
/* For occlusion queries, results[] points to the device global heap. We
* need to remap indices according to the query pool's allocation.
*/
uint result_index = oq_index ? oq_index[query] : query;
uint idx = result_index * reports_per_query;
for (unsigned i = 0; i < reports_per_query; ++i) {
vk_write_query(dst, i, flags, results[idx + i]);
}
}
if (flags & VK_QUERY_RESULT_WITH_AVAILABILITY_BIT) {
vk_write_query(dst, reports_per_query, flags, available);
}
}
+21
View File
@@ -0,0 +1,21 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright © 2024 Alyssa Rosenzweig
* Copyright © 2024 Valve Corporation
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_QUERY_H
#define KK_QUERY_H
#include "compiler/libcl/libcl.h"
struct libkk_imm_write {
DEVICE(uint32_t) address;
uint32_t value;
};
#define LIBKK_QUERY_UNAVAILABLE (uint64_t)((int64_t)-1)
#endif /* KK_QUERY_H */
+93
View File
@@ -0,0 +1,93 @@
/*
* Copyright 2026 LunarG, Inc.
* Copyright 2026 Google LLC
* Copyright 2023 Alyssa Rosenzweig
* SPDX-License-Identifier: MIT
*/
#include "poly/geometry.h"
#include "poly/tessellator.h"
KERNEL(1)
libkk_tess_setup_indirect(
global struct poly_tess_params *p,
global uint32_t *grids /* output: VS then TCS then tess */,
global struct poly_vertex_params *vp /* output */, global uint32_t *indirect,
uint64_t in_index_buffer, uint32_t in_index_buffer_range_el,
uint32_t in_index_size_B, uint64_t vertex_outputs /* bitfield */,
/* Tess control invocation counter if active, else zero */
global uint32_t *tcs_statistic)
{
uint count = indirect[0], instance_count = indirect[1];
unsigned in_patches = count / p->input_patch_size;
/* TCS invocation counter increments once per-patch */
if (tcs_statistic) {
*tcs_statistic += in_patches;
}
size_t draw_stride = 5 * sizeof(uint32_t);
unsigned unrolled_patches = in_patches * instance_count;
uint32_t alloc = 0;
uint32_t tcs_out_offs = alloc;
alloc += unrolled_patches * p->tcs_stride_el * 4;
uint32_t patch_coord_offs = alloc;
alloc += unrolled_patches * 4;
uint32_t count_offs = alloc;
alloc += unrolled_patches * sizeof(uint32_t);
uint vb_offs = alloc;
uint vb_size = poly_tcs_in_size(count * instance_count, vertex_outputs);
alloc += vb_size;
/* Allocate all patch calculations in one go */
global uchar *blob = poly_heap_alloc(p->heap, alloc);
p->tcs_buffer = (global float *)(blob + tcs_out_offs);
p->patches_per_instance = in_patches;
p->coord_allocs = (global uint *)(blob + patch_coord_offs);
p->nr_patches = unrolled_patches;
vp->output_buffer = (uintptr_t)(blob + vb_offs);
vp->outputs = vertex_outputs;
p->counts = (global uint32_t *)(blob + count_offs);
if (vp) {
vp->verts_per_instance = count;
}
/* If indexing is enabled, the third word is the offset into the index buffer
* in elements. Apply that offset now that we have it. For a hardware
* indirect draw, the hardware would do this for us, but for software input
* assembly we need to do it ourselves.
*
* XXX: Deduplicate?
*/
if (in_index_size_B) {
/* TODO_KOSMICKRISP Use poly_index_buffer and implement
* load_ro_sink_address_poly */
vp->index_buffer = in_index_buffer + (indirect[2] * in_index_size_B);
vp->index_buffer_range_el =
poly_index_buffer_range_el(in_index_buffer_range_el, indirect[2]);
}
/* VS grid size */
grids[0] = count;
grids[1] = instance_count;
grids[2] = 1;
/* TCS grid size */
grids[3] = in_patches * p->output_patch_size;
grids[4] = instance_count;
grids[5] = 1;
/* Tess grid size */
grids[6] = unrolled_patches;
grids[7] = 1;
grids[8] = 1;
}
+31
View File
@@ -0,0 +1,31 @@
/*
* Copyright 2026 LunarG, Inc.
* Copyright 2026 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "poly/cl/tessellator.h"
KERNEL(1)
libkk_tess_isoline(constant struct poly_tess_params *p,
enum poly_tess_mode tess_mode)
{
uint patch = cl_global_id.x;
poly_tess_isoline_process(p, patch, tess_mode);
}
KERNEL(1)
libkk_tess_tri(constant struct poly_tess_params *p,
enum poly_tess_mode tess_mode)
{
uint patch = cl_global_id.x;
poly_tess_tri_process(p, patch, tess_mode);
}
KERNEL(1)
libkk_tess_quad(constant struct poly_tess_params *p,
enum poly_tess_mode tess_mode)
{
uint patch = cl_global_id.x;
poly_tess_quad_process(p, patch, tess_mode);
}
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright 2026 LunarG, Inc.
* Copyright 2026 Google LLC
* Copyright 2024 Valve Corporation
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "poly/tessellator.h"
#define libkk_tessellate(context, grid, barrier, prim, mode, state) \
if (prim == TESS_PRIMITIVE_QUADS) { \
libkk_tess_quad(context, grid, barrier, state, mode); \
} else if (prim == TESS_PRIMITIVE_TRIANGLES) { \
libkk_tess_tri(context, grid, barrier, state, mode); \
} else { \
assert(prim == TESS_PRIMITIVE_ISOLINES); \
libkk_tess_isoline(context, grid, barrier, state, mode); \
}
+59
View File
@@ -0,0 +1,59 @@
# Copyright 2026 LunarG, Inc.
# Copyright 2026 Google LLC
# SPDX-License-Identifier: MIT
libkk_shader_files = files(
'kk_draws.cl',
'kk_geometry.cl',
'kk_query.cl',
'kk_tessellation.cl',
'kk_tessellator.cl',
)
libkk_spv = custom_target(
input : libkk_shader_files,
output : 'libkk.spv',
command : [
prog_mesa_clc, '-o', '@OUTPUT@', '--depfile', '@DEPFILE@',
libkk_shader_files, '--',
'-I' + join_paths(meson.project_source_root(), 'include'),
'-I' + join_paths(meson.project_source_root(), 'src/compiler/libcl'),
'-I' + join_paths(meson.project_source_root(), 'src/poly/cl'),
'-I' + join_paths(meson.current_source_dir(), '.'),
'-I' + join_paths(meson.current_source_dir(), '../../'),
'-I' + join_paths(meson.current_source_dir(), 'shaders'),
cl_args,
],
depfile : 'libkk_shaders.h.d',
)
libkk = custom_target(
input : libkk_spv,
output : ['libkk.cpp', 'libkk.h'],
command : [prog_vtn_bindgen2, libkk_spv, '@OUTPUT0@', '@OUTPUT1@'],
)
idep_libkk = declare_dependency(
sources : [libkk],
include_directories : include_directories('.'),
)
libkk_shaders = custom_target(
input : libkk_spv,
output : ['libkk_shaders.h', 'libkk_shaders.c'],
command : [prog_kk_clc, libkk_spv, '@OUTPUT0@', '@OUTPUT1@']
)
# TODO_KOSMICKRISP We can get rid of this library once we remove the generated entrypoints for the trampoline
libkk_shaders_lib = static_library(
'libkk_shaders',
sources : [libkk_shaders],
link_with : [libpoly_nir],
dependencies : [idep_nir, idep_mesautil, idep_libpoly],
)
idep_libkk_shaders_h = declare_dependency(
sources : [libkk_shaders[0]],
include_directories : include_directories('.'),
link_with : libkk_shaders_lib,
)
+18
View File
@@ -0,0 +1,18 @@
# Copyright 2025 LunarG, Inc.
# Copyright 2025 Google LLC
# SPDX-License-Identifier: MIT
subdir('bridge')
subdir('compiler')
subdir('util')
subdir('clc')
subdir('libkk')
subdir('vulkan')
executable(
'kosmicomp',
files('kosmicomp.c'),
dependencies : [idep_nir, idep_vtn, idep_vulkan_runtime_headers, idep_vulkan_util_headers],
link_with: [libkk],
link_args: ['-Wl,-undefined,dynamic_lookup'],
)
@@ -0,0 +1,195 @@
# coding=utf-8
COPYRIGHT = """\
/*
* Copyright 2020 Intel Corporation
* Copyright 2025 LunarG, Inc
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sub license, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
"""
import argparse
import os
from mako.template import Template
# Mesa-local imports must be declared in meson variable
# '{file_without_suffix}_depend_files'.
from vk_entrypoints import get_entrypoints_from_xml
TEMPLATE_H = Template(COPYRIGHT + """\
/* This file generated from ${filename}, don't edit directly. */
#ifndef VK_DISPATCH_TRAMPOLINES_H
#define VK_DISPATCH_TRAMPOLINES_H
#include "vk_dispatch_table.h"
#ifdef __cplusplus
extern "C" {
#endif
extern struct vk_physical_device_dispatch_table kk_physical_device_trampolines;
extern struct vk_device_dispatch_table kk_device_trampolines;
#ifdef __cplusplus
}
#endif
#endif /* VK_DISPATCH_TRAMPOLINES_H */
""")
TEMPLATE_C = Template(COPYRIGHT + """\
/* This file generated from ${filename}, don't edit directly. */
#include "kk_device.h"
#include "kk_dispatch_trampolines.h"
#include "vk_object.h"
#include "vk_physical_device.h"
% for e in entrypoints:
% if not e.is_physical_device_entrypoint() or e.alias:
<% continue %>
% endif
% if e.guard is not None:
#ifdef ${e.guard}
% endif
static VKAPI_ATTR ${e.return_type} VKAPI_CALL
${e.prefixed_name('kk_tramp')}(${e.decl_params()})
{
<% assert e.params[0].type == 'VkPhysicalDevice' %>
VK_FROM_HANDLE(vk_physical_device, vk_physical_device, ${e.params[0].name});
% if e.return_type == 'void':
vk_physical_device->dispatch_table.${e.name}(${e.call_params()});
% else:
return vk_physical_device->dispatch_table.${e.name}(${e.call_params()});
% endif
}
% if e.guard is not None:
#endif
% endif
% endfor
struct vk_physical_device_dispatch_table kk_physical_device_trampolines = {
% for e in entrypoints:
% if not e.is_physical_device_entrypoint() or e.alias:
<% continue %>
% endif
% if e.guard is not None:
#ifdef ${e.guard}
% endif
.${e.name} = ${e.prefixed_name('kk_tramp')},
% if e.guard is not None:
#endif
% endif
% endfor
};
% for e in entrypoints:
% if not e.is_device_entrypoint() or e.alias:
<% continue %>
% endif
% if e.guard is not None:
#ifdef ${e.guard}
% endif
static VKAPI_ATTR ${e.return_type} VKAPI_CALL
${e.prefixed_name('kk_tramp')}(${e.decl_params()})
{
% if e.params[0].type == 'VkDevice':
VK_FROM_HANDLE(kk_device, kk_device, ${e.params[0].name});
% if e.return_type == 'void':
kk_device->exposed_dispatch_table.${e.name}(${e.call_params()});
% else:
return kk_device->exposed_dispatch_table.${e.name}(${e.call_params()});
% endif
% elif e.params[0].type in ('VkCommandBuffer', 'VkQueue'):
struct vk_object_base *vk_object = (struct vk_object_base *)${e.params[0].name};
struct kk_device *kk_device = container_of(vk_object->device, struct kk_device, vk);
% if e.return_type == 'void':
kk_device->exposed_dispatch_table.${e.name}(${e.call_params()});
% else:
return kk_device->exposed_dispatch_table.${e.name}(${e.call_params()});
% endif
% else:
assert(!"Unhandled device child trampoline case: ${e.params[0].type}");
% endif
}
% if e.guard is not None:
#endif
% endif
% endfor
struct vk_device_dispatch_table kk_device_trampolines = {
% for e in entrypoints:
% if not e.is_device_entrypoint() or e.alias:
<% continue %>
% endif
% if e.guard is not None:
#ifdef ${e.guard}
% endif
.${e.name} = ${e.prefixed_name('kk_tramp')},
% if e.guard is not None:
#endif
% endif
% endfor
};
""")
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--out-c', help='Output C file.')
parser.add_argument('--out-h', help='Output H file.')
parser.add_argument('--beta', required=True, help='Enable beta extensions.')
parser.add_argument('--xml',
help='Vulkan API XML file.',
required=True,
action='append',
dest='xml_files')
args = parser.parse_args()
entrypoints = get_entrypoints_from_xml(args.xml_files, args.beta)
# For outputting entrypoints.h we generate a anv_EntryPoint() prototype
# per entry point.
try:
if args.out_h:
with open(args.out_h, 'w', encoding='utf-8') as f:
f.write(TEMPLATE_H.render(entrypoints=entrypoints,
filename=os.path.basename(__file__)))
if args.out_c:
with open(args.out_c, 'w', encoding='utf-8') as f:
f.write(TEMPLATE_C.render(entrypoints=entrypoints,
filename=os.path.basename(__file__)))
except Exception:
# In the event there's an error, this imports some helpers from mako
# to print a useful stack trace and prints it, then exits with
# status 1, if python is run with debug; otherwise it just raises
# the exception
if __debug__:
import sys
from mako import exceptions
sys.stderr.write(exceptions.text_error_template().render() + '\n')
sys.exit(1)
raise
if __name__ == '__main__':
main()
+16
View File
@@ -0,0 +1,16 @@
# Copyright © 2025 LunarG, Inc
# SPDX-License-Identifier: MIT
kk_dispatch_trampolines_gen = files('kk_dispatch_trampolines_gen.py')
kk_dispatch_trampolines = custom_target(
'kk_dispatch_trampolines',
input : [kk_dispatch_trampolines_gen, vk_api_xml],
output : ['kk_dispatch_trampolines.c', 'kk_dispatch_trampolines.h'],
command : [
prog_python, '@INPUT0@', '--xml', '@INPUT1@',
'--out-c', '@OUTPUT0@', '--out-h', '@OUTPUT1@',
'--beta', with_vulkan_beta.to_string()
],
depend_files : vk_dispatch_trampolines_gen_depend_files,
)
+147
View File
@@ -0,0 +1,147 @@
# Copyright 2020 Intel Corporation
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sub license, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice (including the
# next paragraph) shall be included in all copies or substantial portions
# of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
# IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import xml.etree.ElementTree as et
from collections import OrderedDict, namedtuple
# Mesa-local imports must be declared in meson variable
# '{file_without_suffix}_depend_files'.
from vk_extensions import get_all_required, filter_api
EntrypointParam = namedtuple('EntrypointParam', 'type name decl len')
class EntrypointBase:
def __init__(self, name):
assert name.startswith('vk')
self.name = name[2:]
self.alias = None
self.guard = None
self.entry_table_index = None
# Extensions which require this entrypoint
self.core_version = None
self.extensions = []
def prefixed_name(self, prefix):
return prefix + '_' + self.name
class Entrypoint(EntrypointBase):
def __init__(self, name, return_type, params):
super(Entrypoint, self).__init__(name)
self.return_type = return_type
self.params = params
self.guard = None
self.aliases = []
self.disp_table_index = None
def is_physical_device_entrypoint(self):
return self.params[0].type in ('VkPhysicalDevice', )
def is_device_entrypoint(self):
return self.params[0].type in ('VkDevice', 'VkCommandBuffer', 'VkQueue')
def decl_params(self, start=0):
return ', '.join(p.decl for p in self.params[start:])
def call_params(self, start=0):
return ', '.join(p.name for p in self.params[start:])
class EntrypointAlias(EntrypointBase):
def __init__(self, name, entrypoint):
super(EntrypointAlias, self).__init__(name)
self.alias = entrypoint
entrypoint.aliases.append(self)
def is_physical_device_entrypoint(self):
return self.alias.is_physical_device_entrypoint()
def is_device_entrypoint(self):
return self.alias.is_device_entrypoint()
def prefixed_name(self, prefix):
return self.alias.prefixed_name(prefix)
@property
def params(self):
return self.alias.params
@property
def return_type(self):
return self.alias.return_type
@property
def disp_table_index(self):
return self.alias.disp_table_index
def decl_params(self):
return self.alias.decl_params()
def call_params(self):
return self.alias.call_params()
def get_entrypoints(doc, api, beta):
"""Extract the entry points from the registry."""
entrypoints = OrderedDict()
required = get_all_required(doc, 'command', api, beta)
for command in doc.findall('./commands/command'):
if not filter_api(command, api):
continue
if 'alias' in command.attrib:
name = command.attrib['name']
target = command.attrib['alias']
e = EntrypointAlias(name, entrypoints[target])
else:
name = command.find('./proto/name').text
ret_type = command.find('./proto/type').text
params = [EntrypointParam(
type=p.find('./type').text,
name=p.find('./name').text,
decl=''.join(p.itertext()),
len=p.attrib.get('altlen', p.attrib.get('len', None))
) for p in command.findall('./param') if filter_api(p, api)]
# They really need to be unique
e = Entrypoint(name, ret_type, params)
if name not in required:
continue
r = required[name]
e.core_version = r.core_version
e.extensions = r.extensions
e.guard = r.guard
assert name not in entrypoints, name
entrypoints[name] = e
return entrypoints.values()
def get_entrypoints_from_xml(xml_files, beta, api='vulkan'):
entrypoints = []
for filename in xml_files:
doc = et.parse(filename)
entrypoints += get_entrypoints(doc, api, beta)
return entrypoints
+371
View File
@@ -0,0 +1,371 @@
import copy
import re
import xml.etree.ElementTree as et
def get_api_list(s):
apis = []
for a in s.split(','):
if a == 'disabled':
continue
assert a in ('vulkan', 'vulkansc')
apis.append(a)
return apis
class Extension:
def __init__(self, name, number, ext_version):
self.name = name
self.type = None
self.number = number
self.platform = None
self.provisional = False
self.ext_version = int(ext_version)
self.supported = []
def from_xml(ext_elem):
name = ext_elem.attrib['name']
number = int(ext_elem.attrib['number'])
supported = get_api_list(ext_elem.attrib['supported'])
if name == 'VK_ANDROID_native_buffer':
assert not supported
supported = ['vulkan']
if not supported:
return Extension(name, number, 0)
version = None
for enum_elem in ext_elem.findall('.require/enum'):
if enum_elem.attrib['name'].endswith('_SPEC_VERSION'):
# Skip alias SPEC_VERSIONs
if 'value' in enum_elem.attrib:
assert version is None
version = int(enum_elem.attrib['value'])
assert version is not None
ext = Extension(name, number, version)
ext.type = ext_elem.attrib['type']
ext.platform = ext_elem.attrib.get('platform', None)
ext.provisional = ext_elem.attrib.get('provisional', False)
ext.supported = supported
return ext
def c_android_condition(self):
# if it's an EXT or vendor extension, it's allowed
if not self.name.startswith(ANDROID_EXTENSION_WHITELIST_PREFIXES):
return 'true'
allowed_version = ALLOWED_ANDROID_VERSION.get(self.name, None)
if allowed_version is None:
return 'false'
return 'ANDROID_API_LEVEL >= %d' % (allowed_version)
class ApiVersion:
def __init__(self, version):
self.version = version
class VkVersion:
def __init__(self, string):
split = string.split('.')
self.major = int(split[0])
self.minor = int(split[1])
if len(split) > 2:
assert len(split) == 3
self.patch = int(split[2])
else:
self.patch = None
# Sanity check. The range bits are required by the definition of the
# VK_MAKE_VERSION macro
assert self.major < 1024 and self.minor < 1024
assert self.patch is None or self.patch < 4096
assert str(self) == string
def __str__(self):
ver_list = [str(self.major), str(self.minor)]
if self.patch is not None:
ver_list.append(str(self.patch))
return '.'.join(ver_list)
def c_vk_version(self):
ver_list = [str(self.major), str(self.minor), str(self.patch or 0)]
return 'VK_MAKE_VERSION(' + ', '.join(ver_list) + ')'
def __int_ver(self):
# This is just an expansion of VK_VERSION
return (self.major << 22) | (self.minor << 12) | (self.patch or 0)
def __gt__(self, other):
# If only one of them has a patch version, "ignore" it by making
# other's patch version match self.
if (self.patch is None) != (other.patch is None):
other = copy.copy(other)
other.patch = self.patch
return self.__int_ver() > other.__int_ver()
# Sort the extension list the way we expect: KHR, then EXT, then vendors
# alphabetically. For digits, read them as a whole number sort that.
# eg.: VK_KHR_8bit_storage < VK_KHR_16bit_storage < VK_EXT_acquire_xlib_display
def extension_order(ext):
order = []
for substring in re.split('(KHR|EXT|[0-9]+)', ext.name):
if substring == 'KHR':
order.append(1)
if substring == 'EXT':
order.append(2)
elif substring.isdigit():
order.append(int(substring))
else:
order.append(substring)
return order
def get_all_exts_from_xml(xml, api='vulkan'):
""" Get a list of all Vulkan extensions. """
xml = et.parse(xml)
extensions = []
for ext_elem in xml.findall('.extensions/extension'):
ext = Extension.from_xml(ext_elem)
if api in ext.supported:
extensions.append(ext)
return sorted(extensions, key=extension_order)
def init_exts_from_xml(xml, extensions, platform_defines):
""" Walk the Vulkan XML and fill out extra extension information. """
xml = et.parse(xml)
ext_name_map = {}
for ext in extensions:
ext_name_map[ext.name] = ext
# KHR_display is missing from the list.
platform_defines.append('VK_USE_PLATFORM_DISPLAY_KHR')
for platform in xml.findall('./platforms/platform'):
platform_defines.append(platform.attrib['protect'])
for ext_elem in xml.findall('.extensions/extension'):
ext_name = ext_elem.attrib['name']
if ext_name not in ext_name_map:
continue
ext = ext_name_map[ext_name]
ext.type = ext_elem.attrib['type']
class Requirements:
def __init__(self, core_version=None):
self.core_version = core_version
self.extensions = []
self.guard = None
def add_extension(self, ext):
for e in self.extensions:
if e == ext:
return;
assert e.name != ext.name
self.extensions.append(ext)
def filter_api(elem, api):
if 'api' not in elem.attrib:
return True
return api in elem.attrib['api'].split(',')
def get_alias(aliases, name):
if name in aliases:
# in case the spec registry adds an alias chain later
return get_alias(aliases, aliases[name])
return name
def get_all_required(xml, thing, api, beta):
things = {}
aliases = {}
for struct in xml.findall('./types/type[@category="struct"][@alias]'):
if not filter_api(struct, api):
continue
name = struct.attrib['name']
alias = struct.attrib['alias']
aliases[name] = alias
for feature in xml.findall('./feature'):
if not filter_api(feature, api):
continue
version = VkVersion(feature.attrib['number'])
for t in feature.findall('./require/' + thing):
name = t.attrib['name']
assert name not in things
things[name] = Requirements(core_version=version)
for extension in xml.findall('.extensions/extension'):
ext = Extension.from_xml(extension)
if api not in ext.supported:
continue
if beta != 'true' and ext.provisional:
continue
for require in extension.findall('./require'):
if not filter_api(require, api):
continue
for t in require.findall('./' + thing):
name = get_alias(aliases, t.attrib['name'])
r = things.setdefault(name, Requirements())
r.add_extension(ext)
platform_defines = {}
for platform in xml.findall('./platforms/platform'):
name = platform.attrib['name']
define = platform.attrib['protect']
platform_defines[name] = define
for req in things.values():
if req.core_version is not None:
continue
for ext in req.extensions:
if ext.platform in platform_defines:
req.guard = platform_defines[ext.platform]
break
return things
# Mapping between extension name and the android version in which the extension
# was whitelisted in Android CTS's dEQP-VK.info.device_extensions and
# dEQP-VK.api.info.android.no_unknown_extensions, excluding those blocked by
# android.graphics.cts.VulkanFeaturesTest#testVulkanBlockedExtensions.
ALLOWED_ANDROID_VERSION = {
# checkInstanceExtensions on oreo-cts-release
"VK_KHR_surface": 26,
"VK_KHR_display": 26,
"VK_KHR_android_surface": 26,
"VK_KHR_mir_surface": 26,
"VK_KHR_wayland_surface": 26,
"VK_KHR_win32_surface": 26,
"VK_KHR_xcb_surface": 26,
"VK_KHR_xlib_surface": 26,
"VK_KHR_get_physical_device_properties2": 26,
"VK_KHR_get_surface_capabilities2": 26,
"VK_KHR_external_memory_capabilities": 26,
"VK_KHR_external_semaphore_capabilities": 26,
"VK_KHR_external_fence_capabilities": 26,
# on pie-cts-release
"VK_KHR_device_group_creation": 28,
"VK_KHR_get_display_properties2": 28,
# on android10-tests-release
"VK_KHR_surface_protected_capabilities": 29,
# on android13-tests-release
"VK_KHR_portability_enumeration": 33,
# checkDeviceExtensions on oreo-cts-release
"VK_KHR_swapchain": 26,
"VK_KHR_display_swapchain": 26,
"VK_KHR_sampler_mirror_clamp_to_edge": 26,
"VK_KHR_shader_draw_parameters": 26,
"VK_KHR_maintenance1": 26,
"VK_KHR_push_descriptor": 26,
"VK_KHR_descriptor_update_template": 26,
"VK_KHR_incremental_present": 26,
"VK_KHR_shared_presentable_image": 26,
"VK_KHR_storage_buffer_storage_class": 26,
"VK_KHR_16bit_storage": 26,
"VK_KHR_get_memory_requirements2": 26,
"VK_KHR_external_memory": 26,
"VK_KHR_external_memory_fd": 26,
"VK_KHR_external_memory_win32": 26,
"VK_KHR_external_semaphore": 26,
"VK_KHR_external_semaphore_fd": 26,
"VK_KHR_external_semaphore_win32": 26,
"VK_KHR_external_fence": 26,
"VK_KHR_external_fence_fd": 26,
"VK_KHR_external_fence_win32": 26,
"VK_KHR_win32_keyed_mutex": 26,
"VK_KHR_dedicated_allocation": 26,
"VK_KHR_variable_pointers": 26,
"VK_KHR_relaxed_block_layout": 26,
"VK_KHR_bind_memory2": 26,
"VK_KHR_maintenance2": 26,
"VK_KHR_image_format_list": 26,
"VK_KHR_sampler_ycbcr_conversion": 26,
# on oreo-mr1-cts-release
"VK_KHR_draw_indirect_count": 27,
# on pie-cts-release
"VK_KHR_device_group": 28,
"VK_KHR_multiview": 28,
"VK_KHR_maintenance3": 28,
"VK_KHR_create_renderpass2": 28,
"VK_KHR_driver_properties": 28,
# on android10-tests-release
"VK_KHR_shader_float_controls": 29,
"VK_KHR_shader_float16_int8": 29,
"VK_KHR_8bit_storage": 29,
"VK_KHR_depth_stencil_resolve": 29,
"VK_KHR_swapchain_mutable_format": 29,
"VK_KHR_shader_atomic_int64": 29,
"VK_KHR_vulkan_memory_model": 29,
"VK_KHR_swapchain_mutable_format": 29,
"VK_KHR_uniform_buffer_standard_layout": 29,
# on android11-tests-release
"VK_KHR_imageless_framebuffer": 30,
"VK_KHR_shader_subgroup_extended_types": 30,
"VK_KHR_buffer_device_address": 30,
"VK_KHR_separate_depth_stencil_layouts": 30,
"VK_KHR_timeline_semaphore": 30,
"VK_KHR_spirv_1_4": 30,
"VK_KHR_pipeline_executable_properties": 30,
"VK_KHR_shader_clock": 30,
# blocked by testVulkanBlockedExtensions
# "VK_KHR_performance_query": 30,
"VK_KHR_shader_non_semantic_info": 30,
"VK_KHR_copy_commands2": 30,
# on android12-tests-release
"VK_KHR_shader_terminate_invocation": 31,
"VK_KHR_ray_tracing_pipeline": 31,
"VK_KHR_ray_query": 31,
"VK_KHR_acceleration_structure": 31,
"VK_KHR_pipeline_library": 31,
"VK_KHR_deferred_host_operations": 31,
"VK_KHR_fragment_shading_rate": 31,
"VK_KHR_zero_initialize_workgroup_memory": 31,
"VK_KHR_workgroup_memory_explicit_layout": 31,
"VK_KHR_synchronization2": 31,
"VK_KHR_shader_integer_dot_product": 31,
# on android13-tests-release
"VK_KHR_dynamic_rendering": 33,
"VK_KHR_format_feature_flags2": 33,
"VK_KHR_global_priority": 33,
"VK_KHR_maintenance4": 33,
"VK_KHR_portability_subset": 33,
"VK_KHR_present_id": 33,
"VK_KHR_present_wait": 33,
"VK_KHR_shader_subgroup_uniform_control_flow": 33,
# testNoUnknownExtensions on oreo-cts-release
"VK_GOOGLE_display_timing": 26,
# on pie-cts-release
"VK_ANDROID_external_memory_android_hardware_buffer": 28,
# on android11-tests-release
"VK_GOOGLE_decorate_string": 30,
"VK_GOOGLE_hlsl_functionality1": 30,
# on android13-tests-release
"VK_GOOGLE_surfaceless_query": 33,
# this HAL extension is always allowed and will be filtered out by the
# loader
"VK_ANDROID_native_buffer": 26,
}
# Extensions with these prefixes are checked in Android CTS, and thus must be
# whitelisted per the preceding dict.
ANDROID_EXTENSION_WHITELIST_PREFIXES = (
"VK_KHX",
"VK_KHR",
"VK_GOOGLE",
"VK_ANDROID"
)
+90
View File
@@ -0,0 +1,90 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_bo.h"
#include "kk_device.h"
#include "kosmickrisp/bridge/mtl_bridge.h"
#include "util/u_memory.h"
VkResult
kk_alloc_bo(struct kk_device *dev, struct vk_object_base *log_obj,
uint64_t size_B, uint64_t align_B, struct kk_bo **bo_out)
{
VkResult result = VK_SUCCESS;
// TODO_KOSMICKRISP: Probably requires handling the buffer maximum 256MB
uint64_t minimum_alignment = 0u;
mtl_heap_buffer_size_and_align_with_length(dev->mtl_handle, &size_B,
&minimum_alignment);
minimum_alignment = MAX2(minimum_alignment, align_B);
#if DETECT_ARCH_X86_64
/* KK_WORKAROUND_8 */
if (!(dev->disabled_workarounds & BITFIELD64_BIT(8))) {
minimum_alignment = MAX2(minimum_alignment, 16384);
}
#endif
size_B = align64(size_B, minimum_alignment);
mtl_heap *handle =
mtl_new_heap(dev->mtl_handle, size_B, KK_MTL_RESOURCE_OPTIONS);
if (handle == NULL) {
result = vk_errorf(log_obj, VK_ERROR_OUT_OF_DEVICE_MEMORY, "%m");
goto fail_heap;
}
mtl_buffer *map = mtl_new_buffer_with_length(handle, size_B, 0u);
if (map == NULL) {
result = vk_errorf(log_obj, VK_ERROR_OUT_OF_DEVICE_MEMORY, "%m");
goto fail_map;
}
struct kk_bo *bo = CALLOC_STRUCT(kk_bo);
if (bo == NULL) {
result = vk_errorf(log_obj, VK_ERROR_OUT_OF_HOST_MEMORY, "%m");
goto fail_alloc;
}
bo->mtl_handle = handle;
bo->size_B = size_B;
bo->map = map;
bo->gpu = mtl_buffer_get_gpu_address(map);
bo->cpu = mtl_get_contents(map);
kk_device_add_heap_to_residency_set(dev, handle);
*bo_out = bo;
return result;
fail_alloc:
mtl_release(map);
fail_map:
mtl_release(handle);
fail_heap:
return result;
}
void
kk_destroy_bo(struct kk_device *dev, struct kk_bo *bo)
{
/* We may only have a mapped buffer, for example if the memory was imported
* from a host pointer */
if (bo->mtl_handle)
kk_device_remove_heap_from_residency_set(dev, bo->mtl_handle);
else
kk_device_remove_buffer_from_residency_set(dev, bo->map);
mtl_release(bo->map);
if (bo->mtl_handle)
mtl_release(bo->mtl_handle);
FREE(bo);
}
+41
View File
@@ -0,0 +1,41 @@
/*
* Copyright © 2025 LunarG, Inc
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_BO_H
#define KK_BO_H 1
#include "kosmickrisp/bridge/mtl_types.h"
#include "vulkan/vulkan_core.h"
#include <inttypes.h>
struct kk_device;
struct vk_object_base;
struct kk_bo {
mtl_heap *mtl_handle;
mtl_buffer *map;
uint64_t size_B;
uint64_t gpu; // GPU address
void *cpu; // CPU address
};
struct kk_ptr {
void *cpu;
uint64_t gpu;
/* Pointer in terms of a Metal buffer and offset */
mtl_buffer *buffer;
uint32_t offset;
};
VkResult kk_alloc_bo(struct kk_device *dev, struct vk_object_base *log_obj,
uint64_t size_B, uint64_t align_B, struct kk_bo **bo_out);
void kk_destroy_bo(struct kk_device *dev, struct kk_bo *bo);
#endif /* KK_BO_H */
+222
View File
@@ -0,0 +1,222 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_buffer.h"
#include "kk_device.h"
#include "kk_entrypoints.h"
#include "kk_physical_device.h"
#include "kosmickrisp/bridge/mtl_bridge.h"
static uint64_t
kk_get_buffer_alignment(const struct kk_physical_device *pdev, uint64_t size,
VkBufferUsageFlags2KHR usage_flags,
VkBufferCreateFlags create_flags)
{
uint64_t alignment;
mtl_heap_buffer_size_and_align_with_length(pdev->mtl_dev_handle, &size,
&alignment);
/** TODO_KOSMICKRISP Metal requires that texel buffers be aligned to the
* format they'll use. Since we won't be able to know the format until the
* view is created, we should align to the worst case scenario. For this, we
* need to request all supported format alignments and take the largest one.
*/
return alignment;
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_CreateBuffer(VkDevice device, const VkBufferCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator, VkBuffer *pBuffer)
{
VK_FROM_HANDLE(kk_device, dev, device);
struct kk_physical_device *pdev = kk_device_physical(dev);
struct kk_buffer *buffer;
if (pCreateInfo->size > pdev->info.max_buffer_size)
return vk_error(dev, VK_ERROR_OUT_OF_DEVICE_MEMORY);
buffer =
vk_buffer_create(&dev->vk, pCreateInfo, pAllocator, sizeof(*buffer));
if (!buffer)
return vk_error(dev, VK_ERROR_OUT_OF_HOST_MEMORY);
*pBuffer = kk_buffer_to_handle(buffer);
return VK_SUCCESS;
}
VKAPI_ATTR void VKAPI_CALL
kk_DestroyBuffer(VkDevice device, VkBuffer _buffer,
const VkAllocationCallbacks *pAllocator)
{
VK_FROM_HANDLE(kk_device, dev, device);
VK_FROM_HANDLE(kk_buffer, buffer, _buffer);
if (!buffer)
return;
if (buffer->mtl_handle)
mtl_release(buffer->mtl_handle);
vk_buffer_destroy(&dev->vk, pAllocator, &buffer->vk);
}
VKAPI_ATTR void VKAPI_CALL
kk_GetDeviceBufferMemoryRequirements(
VkDevice device, const VkDeviceBufferMemoryRequirements *pInfo,
VkMemoryRequirements2 *pMemoryRequirements)
{
VK_FROM_HANDLE(kk_device, dev, device);
struct kk_physical_device *pdev = kk_device_physical(dev);
const uint64_t alignment = kk_get_buffer_alignment(
pdev, pInfo->pCreateInfo->size, pInfo->pCreateInfo->usage,
pInfo->pCreateInfo->flags);
pMemoryRequirements->memoryRequirements = (VkMemoryRequirements){
.size = align64(pInfo->pCreateInfo->size, alignment),
.alignment = alignment,
.memoryTypeBits = BITFIELD_MASK(pdev->mem_type_count),
};
vk_foreach_struct_const(ext, pMemoryRequirements->pNext) {
switch (ext->sType) {
case VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS: {
VkMemoryDedicatedRequirements *dedicated = (void *)ext;
dedicated->prefersDedicatedAllocation = false;
dedicated->requiresDedicatedAllocation = false;
break;
}
default:
vk_debug_ignored_stype(ext->sType);
break;
}
}
}
VKAPI_ATTR void VKAPI_CALL
kk_GetPhysicalDeviceExternalBufferProperties(
VkPhysicalDevice physicalDevice,
const VkPhysicalDeviceExternalBufferInfo *pExternalBufferInfo,
VkExternalBufferProperties *pExternalBufferProperties)
{
/* The Vulkan 1.3.256 spec says:
*
* VUID-VkPhysicalDeviceExternalBufferInfo-handleType-parameter
*
* "handleType must be a valid VkExternalMemoryHandleTypeFlagBits value"
*
* This differs from VkPhysicalDeviceExternalImageFormatInfo, which
* surprisingly permits handleType == 0.
*/
assert(pExternalBufferInfo->handleType != 0);
/* All of the current flags are for sparse which we don't support yet.
* Even when we do support it, doing sparse on external memory sounds
* sketchy. Also, just disallowing flags is the safe option.
*/
if (pExternalBufferInfo->flags)
goto unsupported;
switch (pExternalBufferInfo->handleType) {
case VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLHEAP_BIT_EXT:
pExternalBufferProperties->externalMemoryProperties =
kk_mtlheap_mem_props;
return;
case VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT:
case VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_MAPPED_FOREIGN_MEMORY_BIT_EXT:
pExternalBufferProperties->externalMemoryProperties = kk_host_mem_props;
return;
default:
goto unsupported;
}
unsupported:
/* From the Vulkan 1.3.256 spec:
*
* compatibleHandleTypes must include at least handleType.
*/
pExternalBufferProperties->externalMemoryProperties =
(VkExternalMemoryProperties){
.compatibleHandleTypes = pExternalBufferInfo->handleType,
};
}
static VkResult
kk_bind_buffer_memory(struct kk_device *dev, const VkBindBufferMemoryInfo *info)
{
// Do the actual memory binding
VK_FROM_HANDLE(kk_device_memory, mem, info->memory);
VK_FROM_HANDLE(kk_buffer, buffer, info->buffer);
if (mem->bo->mtl_handle)
buffer->mtl_handle = mtl_new_buffer_with_length(
mem->bo->mtl_handle, buffer->vk.size, info->memoryOffset);
else {
/* If the memory is not heap backed, for example if we imported a host
* pointer, use the mapped buffer directly and retain a reference */
buffer->mtl_handle = mem->bo->map;
mtl_retain(buffer->mtl_handle);
}
buffer->vk.device_address = mtl_buffer_get_gpu_address(buffer->mtl_handle);
/* We need Metal to give us a CPU mapping so it correctly captures the
* data in the GPU debugger... */
mtl_get_contents(buffer->mtl_handle);
return VK_SUCCESS;
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_BindBufferMemory2(VkDevice device, uint32_t bindInfoCount,
const VkBindBufferMemoryInfo *pBindInfos)
{
VK_FROM_HANDLE(kk_device, dev, device);
VkResult first_error_or_success = VK_SUCCESS;
for (uint32_t i = 0; i < bindInfoCount; ++i) {
VkResult result = kk_bind_buffer_memory(dev, &pBindInfos[i]);
const VkBindMemoryStatusKHR *status =
vk_find_struct_const(pBindInfos[i].pNext, BIND_MEMORY_STATUS_KHR);
if (status != NULL && status->pResult != NULL)
*status->pResult = result;
if (first_error_or_success == VK_SUCCESS)
first_error_or_success = result;
}
return first_error_or_success;
}
VKAPI_ATTR VkDeviceAddress VKAPI_CALL
kk_GetBufferDeviceAddress(UNUSED VkDevice device,
const VkBufferDeviceAddressInfo *pInfo)
{
VK_FROM_HANDLE(kk_buffer, buffer, pInfo->buffer);
return vk_buffer_address(&buffer->vk, 0);
}
VKAPI_ATTR uint64_t VKAPI_CALL
kk_GetBufferOpaqueCaptureAddress(UNUSED VkDevice device,
const VkBufferDeviceAddressInfo *pInfo)
{
VK_FROM_HANDLE(kk_buffer, buffer, pInfo->buffer);
return vk_buffer_address(&buffer->vk, 0);
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_GetBufferOpaqueCaptureDescriptorDataEXT(
VkDevice device, const VkBufferCaptureDescriptorDataInfoEXT *pInfo,
void *pData)
{
return VK_SUCCESS;
}
+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_BUFFER_H
#define KK_BUFFER_H 1
#include "kk_device_memory.h"
#include "kk_private.h"
#include "kosmickrisp/bridge/mtl_types.h"
#include "vk_buffer.h"
struct kk_buffer {
struct vk_buffer vk;
mtl_buffer *mtl_handle;
};
VK_DEFINE_NONDISP_HANDLE_CASTS(kk_buffer, vk.base, VkBuffer,
VK_OBJECT_TYPE_BUFFER)
static inline struct kk_addr_range
kk_buffer_addr_range(const struct kk_buffer *buffer, uint64_t offset,
uint64_t range)
{
if (buffer == NULL)
return (struct kk_addr_range){.range = 0};
return (struct kk_addr_range){
.addr = vk_buffer_address(&buffer->vk, offset),
.range = vk_buffer_range(&buffer->vk, offset, range),
};
}
static inline mtl_resource *
kk_buffer_to_mtl_resource(const struct kk_buffer *buffer)
{
if (buffer != NULL) {
return (mtl_resource *)buffer->mtl_handle;
}
return NULL;
}
#endif // KK_BUFFER_H
+128
View File
@@ -0,0 +1,128 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_buffer_view.h"
#include "kk_buffer.h"
#include "kk_device.h"
#include "kk_entrypoints.h"
#include "kk_format.h"
#include "kk_image_layout.h"
#include "kk_nir_lower_vbo.h"
#include "kk_physical_device.h"
#include "kosmickrisp/bridge/mtl_bridge.h"
#include "kosmickrisp/bridge/mtl_format.h"
#include "vk_format.h"
VkFormatFeatureFlags2
kk_get_buffer_format_features(struct kk_physical_device *pdev,
VkFormat vk_format)
{
VkFormatFeatureFlags2 features = 0;
enum pipe_format p_format = vk_format_to_pipe_format(vk_format);
if (p_format == PIPE_FORMAT_NONE)
return 0;
const struct kk_va_format *format = kk_get_va_format(p_format);
if (format) {
if (format->texel_buffer.read)
features |= VK_FORMAT_FEATURE_2_UNIFORM_TEXEL_BUFFER_BIT;
if (format->texel_buffer.write) {
features |= VK_FORMAT_FEATURE_2_STORAGE_TEXEL_BUFFER_BIT;
features |= VK_FORMAT_FEATURE_2_STORAGE_WRITE_WITHOUT_FORMAT_BIT;
features |= VK_FORMAT_FEATURE_2_STORAGE_READ_WITHOUT_FORMAT_BIT;
}
/* Only these formats allow atomics for texel buffers */
if (vk_format == VK_FORMAT_R32_UINT || vk_format == VK_FORMAT_R32_SINT)
features |= VK_FORMAT_FEATURE_2_STORAGE_TEXEL_BUFFER_ATOMIC_BIT;
}
if (kk_vbo_supports_format(p_format))
features |= VK_FORMAT_FEATURE_2_VERTEX_BUFFER_BIT;
return features;
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_CreateBufferView(VkDevice _device, const VkBufferViewCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator,
VkBufferView *pBufferView)
{
VK_FROM_HANDLE(kk_device, dev, _device);
struct kk_buffer_view *view =
vk_buffer_view_create(&dev->vk, pCreateInfo, pAllocator, sizeof(*view));
if (!view)
return vk_error(dev, VK_ERROR_OUT_OF_HOST_MEMORY);
enum pipe_format p_format = vk_format_to_pipe_format(view->vk.format);
const struct kk_va_format *supported_format = kk_get_va_format(p_format);
/* If we reached here, we support reading at least */
enum mtl_texture_usage usage = MTL_TEXTURE_USAGE_SHADER_READ;
if (supported_format->texel_buffer.write)
usage |= MTL_TEXTURE_USAGE_SHADER_WRITE;
/* Only these formats allow atomics for texel buffers */
if (view->vk.format == VK_FORMAT_R32_UINT ||
view->vk.format == VK_FORMAT_R32_SINT)
usage |= MTL_TEXTURE_USAGE_SHADER_ATOMIC;
struct kk_image_layout layout = {
.width_px = view->vk.elements,
.height_px = 1u,
.depth_px = 1u,
.layers = 1u,
.type = MTL_TEXTURE_TYPE_TEXTURE_BUFFER,
.sample_count_sa = 1u,
.levels = 1u,
.linear = true,
.optimized_layout = false,
.usage = usage,
.format = {.pipe = p_format, .mtl = supported_format->mtl_pixel_format},
.swizzle =
{
.red = supported_format->unswizzle.red,
.green = supported_format->unswizzle.green,
.blue = supported_format->unswizzle.blue,
.alpha = supported_format->unswizzle.alpha,
},
.linear_stride_B = view->vk.range,
};
struct kk_buffer *buffer =
container_of(view->vk.buffer, struct kk_buffer, vk);
view->mtl_texel_buffer_handle = mtl_new_texture_with_descriptor_linear(
buffer->mtl_handle, &layout, view->vk.offset);
if (!view->mtl_texel_buffer_handle) {
vk_buffer_view_destroy(&dev->vk, pAllocator, &view->vk);
return vk_error(dev, VK_ERROR_OUT_OF_DEVICE_MEMORY);
}
view->texel_buffer_gpu_id =
mtl_texture_get_gpu_resource_id(view->mtl_texel_buffer_handle);
*pBufferView = kk_buffer_view_to_handle(view);
return VK_SUCCESS;
}
VKAPI_ATTR void VKAPI_CALL
kk_DestroyBufferView(VkDevice _device, VkBufferView bufferView,
const VkAllocationCallbacks *pAllocator)
{
VK_FROM_HANDLE(kk_device, dev, _device);
VK_FROM_HANDLE(kk_buffer_view, view, bufferView);
if (!view)
return;
mtl_release(view->mtl_texel_buffer_handle);
vk_buffer_view_destroy(&dev->vk, pAllocator, &view->vk);
}
+31
View File
@@ -0,0 +1,31 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_BUFFER_VIEW_H
#define KK_BUFFER_VIEW_H 1
#include "kk_private.h"
#include "kosmickrisp/bridge/mtl_types.h"
#include "vk_buffer_view.h"
struct kk_physical_device;
VkFormatFeatureFlags2
kk_get_buffer_format_features(struct kk_physical_device *pdev, VkFormat format);
struct kk_buffer_view {
struct vk_buffer_view vk;
mtl_texture *mtl_texel_buffer_handle;
uint64_t texel_buffer_gpu_id;
};
VK_DEFINE_NONDISP_HANDLE_CASTS(kk_buffer_view, vk.base, VkBufferView,
VK_OBJECT_TYPE_BUFFER_VIEW)
#endif
+579
View File
@@ -0,0 +1,579 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_cmd_buffer.h"
#include "kk_buffer.h"
#include "kk_cmd_pool.h"
#include "kk_descriptor_set_layout.h"
#include "kk_encoder.h"
#include "kk_entrypoints.h"
#include "kosmickrisp/bridge/mtl_bridge.h"
#include "vk_alloc.h"
#include "vk_pipeline_layout.h"
static void
kk_descriptor_state_fini(struct kk_cmd_buffer *cmd,
struct kk_descriptor_state *desc)
{
struct kk_cmd_pool *pool = kk_cmd_buffer_pool(cmd);
for (unsigned i = 0; i < KK_MAX_SETS; i++) {
vk_free(&pool->vk.alloc, desc->push[i]);
desc->push[i] = NULL;
}
}
void
kk_cmd_release_resources(struct kk_device *dev, struct kk_cmd_buffer *cmd)
{
struct kk_cmd_pool *pool = kk_cmd_buffer_pool(cmd);
kk_cmd_release_dynamic_ds_state(cmd);
kk_descriptor_state_fini(cmd, &cmd->state.gfx.descriptors);
kk_descriptor_state_fini(cmd, &cmd->state.cs.descriptors);
kk_cmd_pool_free_bo_list(pool, &cmd->uploader.bos);
/* Release all BOs used as descriptor buffers for submissions */
util_dynarray_foreach(&cmd->large_bos, struct kk_bo *, bo) {
kk_destroy_bo(dev, *bo);
}
util_dynarray_clear(&cmd->large_bos);
}
static void
kk_destroy_cmd_buffer(struct vk_command_buffer *vk_cmd_buffer)
{
struct kk_cmd_buffer *cmd =
container_of(vk_cmd_buffer, struct kk_cmd_buffer, vk);
struct kk_cmd_pool *pool = kk_cmd_buffer_pool(cmd);
vk_command_buffer_finish(&cmd->vk);
struct kk_device *dev = kk_cmd_buffer_device(cmd);
kk_cmd_release_resources(dev, cmd);
util_dynarray_fini(&cmd->large_bos);
vk_free(&pool->vk.alloc, cmd);
}
static VkResult
kk_create_cmd_buffer(struct vk_command_pool *vk_pool,
VkCommandBufferLevel level,
struct vk_command_buffer **cmd_buffer_out)
{
struct kk_cmd_pool *pool = container_of(vk_pool, struct kk_cmd_pool, vk);
struct kk_device *dev = kk_cmd_pool_device(pool);
struct kk_cmd_buffer *cmd;
VkResult result;
cmd = vk_zalloc(&pool->vk.alloc, sizeof(*cmd), 8,
VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
if (cmd == NULL)
return vk_error(dev, VK_ERROR_OUT_OF_HOST_MEMORY);
result =
vk_command_buffer_init(&pool->vk, &cmd->vk, &kk_cmd_buffer_ops, level);
if (result != VK_SUCCESS) {
vk_free(&pool->vk.alloc, cmd);
return result;
}
cmd->large_bos = UTIL_DYNARRAY_INIT;
cmd->vk.dynamic_graphics_state.vi = &cmd->state.gfx._dynamic_vi;
cmd->vk.dynamic_graphics_state.ms.sample_locations =
&cmd->state.gfx._dynamic_sl;
list_inithead(&cmd->uploader.bos);
*cmd_buffer_out = &cmd->vk;
return VK_SUCCESS;
}
static void
kk_reset_cmd_buffer(struct vk_command_buffer *vk_cmd_buffer,
UNUSED VkCommandBufferResetFlags flags)
{
struct kk_cmd_buffer *cmd =
container_of(vk_cmd_buffer, struct kk_cmd_buffer, vk);
struct kk_device *dev = kk_cmd_buffer_device(cmd);
vk_command_buffer_reset(&cmd->vk);
kk_cmd_release_resources(dev, cmd);
cmd->uploader.bo = NULL;
cmd->uploader.offset = 0;
memset(&cmd->state, 0, sizeof(cmd->state));
cmd->uses_heap = false;
}
const struct vk_command_buffer_ops kk_cmd_buffer_ops = {
.create = kk_create_cmd_buffer,
.reset = kk_reset_cmd_buffer,
.destroy = kk_destroy_cmd_buffer,
};
VKAPI_ATTR VkResult VKAPI_CALL
kk_BeginCommandBuffer(VkCommandBuffer commandBuffer,
const VkCommandBufferBeginInfo *pBeginInfo)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
kk_reset_cmd_buffer(&cmd->vk, 0u);
vk_command_buffer_begin(&cmd->vk, pBeginInfo);
return VK_SUCCESS;
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_EndCommandBuffer(VkCommandBuffer commandBuffer)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
return vk_command_buffer_end(&cmd->vk);
}
static bool
kk_can_ignore_barrier(VkAccessFlags2 access, VkPipelineStageFlags2 stage)
{
if (access == VK_ACCESS_2_NONE || stage == VK_PIPELINE_STAGE_2_NONE)
return true;
const VkAccessFlags2 ignore_access =
VK_ACCESS_2_HOST_READ_BIT | VK_ACCESS_2_HOST_WRITE_BIT;
const VkPipelineStageFlags2 ignore_stage = VK_PIPELINE_STAGE_2_HOST_BIT;
return (!(access ^ ignore_access)) || (!(stage ^ ignore_stage));
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdPipelineBarrier2(VkCommandBuffer commandBuffer,
const VkDependencyInfo *pDependencyInfo)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
enum kk_encoder_type last_used = cmd->encoder->main.last_used;
kk_encoder_signal_fence_and_end(cmd);
/* If we were inside a render pass, restart it loading attachments */
if (last_used == KK_ENC_RENDER) {
struct kk_graphics_state *state = &cmd->state.gfx;
assert(state->render_pass_descriptor);
kk_encoder_start_render(cmd, state->render_pass_descriptor,
state->render.view_mask);
kk_cmd_buffer_dirty_all_gfx(cmd);
}
}
static void
kk_bind_descriptor_sets(struct kk_descriptor_state *desc,
const VkBindDescriptorSetsInfoKHR *info)
{
VK_FROM_HANDLE(vk_pipeline_layout, pipeline_layout, info->layout);
/* From the Vulkan 1.3.275 spec:
*
* "When binding a descriptor set (see Descriptor Set Binding) to
* set number N...
*
* If, additionally, the previously bound descriptor set for set
* N was bound using a pipeline layout not compatible for set N,
* then all bindings in sets numbered greater than N are
* disturbed."
*
* This means that, if some earlier set gets bound in such a way that
* it changes set_dynamic_buffer_start[s], this binding is implicitly
* invalidated.
*/
uint8_t dyn_buffer_start =
pipeline_layout->dynamic_descriptor_offset[info->firstSet];
uint32_t next_dyn_offset = 0;
for (uint32_t i = 0; i < info->descriptorSetCount; ++i) {
unsigned s = i + info->firstSet;
VK_FROM_HANDLE(kk_descriptor_set, set, info->pDescriptorSets[i]);
if (desc->sets[s] != set) {
if (set != NULL) {
desc->root.sets[s] = set->addr;
desc->set_sizes[s] = set->size;
} else {
desc->root.sets[s] = 0;
desc->set_sizes[s] = 0;
}
desc->sets[s] = set;
/* Binding descriptors invalidates push descriptors */
desc->push_dirty &= ~BITFIELD_BIT(s);
}
if (pipeline_layout->set_layouts[s] != NULL) {
const struct kk_descriptor_set_layout *set_layout =
vk_to_kk_descriptor_set_layout(pipeline_layout->set_layouts[s]);
if (set != NULL && set_layout->vk.dynamic_descriptor_count > 0) {
for (uint32_t j = 0; j < set_layout->vk.dynamic_descriptor_count;
j++) {
struct kk_buffer_address addr = set->dynamic_buffers[j];
addr.base_addr += info->pDynamicOffsets[next_dyn_offset + j];
desc->root.dynamic_buffers[dyn_buffer_start + j] = addr;
}
next_dyn_offset += set->layout->vk.dynamic_descriptor_count;
}
dyn_buffer_start += set_layout->vk.dynamic_descriptor_count;
} else {
assert(set == NULL);
}
}
assert(dyn_buffer_start <= KK_MAX_DYNAMIC_BUFFERS);
assert(next_dyn_offset <= info->dynamicOffsetCount);
desc->root_dirty = true;
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdBindDescriptorSets2KHR(
VkCommandBuffer commandBuffer,
const VkBindDescriptorSetsInfoKHR *pBindDescriptorSetsInfo)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
if (pBindDescriptorSetsInfo->stageFlags & VK_SHADER_STAGE_ALL_GRAPHICS) {
kk_bind_descriptor_sets(&cmd->state.gfx.descriptors,
pBindDescriptorSetsInfo);
}
if (pBindDescriptorSetsInfo->stageFlags & VK_SHADER_STAGE_COMPUTE_BIT) {
kk_bind_descriptor_sets(&cmd->state.cs.descriptors,
pBindDescriptorSetsInfo);
}
}
static struct kk_push_descriptor_set *
kk_cmd_push_descriptors(struct kk_cmd_buffer *cmd,
struct kk_descriptor_state *desc,
struct kk_descriptor_set_layout *set_layout,
uint32_t set)
{
assert(set < KK_MAX_SETS);
if (unlikely(desc->push[set] == NULL)) {
size_t size = sizeof(*desc->push[set]);
desc->push[set] = vk_zalloc(&cmd->vk.pool->alloc, size, 8,
VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
if (unlikely(desc->push[set] == NULL)) {
vk_command_buffer_set_error(&cmd->vk, VK_ERROR_OUT_OF_HOST_MEMORY);
return NULL;
}
}
/* Pushing descriptors replaces whatever sets are bound */
desc->push[set]->layout = set_layout;
desc->sets[set] = NULL;
desc->push_dirty |= BITFIELD_BIT(set);
return desc->push[set];
}
static void
kk_push_descriptor_set(struct kk_cmd_buffer *cmd,
struct kk_descriptor_state *desc,
const VkPushDescriptorSetInfoKHR *info)
{
VK_FROM_HANDLE(vk_pipeline_layout, pipeline_layout, info->layout);
struct kk_descriptor_set_layout *set_layout =
vk_to_kk_descriptor_set_layout(pipeline_layout->set_layouts[info->set]);
struct kk_push_descriptor_set *push_set =
kk_cmd_push_descriptors(cmd, desc, set_layout, info->set);
if (unlikely(push_set == NULL))
return;
kk_push_descriptor_set_update(push_set, info->descriptorWriteCount,
info->pDescriptorWrites);
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdPushDescriptorSet2KHR(
VkCommandBuffer commandBuffer,
const VkPushDescriptorSetInfoKHR *pPushDescriptorSetInfo)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
if (pPushDescriptorSetInfo->stageFlags & VK_SHADER_STAGE_ALL_GRAPHICS) {
kk_push_descriptor_set(cmd, &cmd->state.gfx.descriptors,
pPushDescriptorSetInfo);
}
if (pPushDescriptorSetInfo->stageFlags & VK_SHADER_STAGE_COMPUTE_BIT) {
kk_push_descriptor_set(cmd, &cmd->state.cs.descriptors,
pPushDescriptorSetInfo);
}
}
static void
kk_push_constants(UNUSED struct kk_cmd_buffer *cmd,
struct kk_descriptor_state *desc,
const VkPushConstantsInfoKHR *info)
{
memcpy(desc->root.push + info->offset, info->pValues, info->size);
desc->root_dirty = true;
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdPushConstants2KHR(VkCommandBuffer commandBuffer,
const VkPushConstantsInfoKHR *pPushConstantsInfo)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
if (pPushConstantsInfo->stageFlags & VK_SHADER_STAGE_ALL_GRAPHICS)
kk_push_constants(cmd, &cmd->state.gfx.descriptors, pPushConstantsInfo);
if (pPushConstantsInfo->stageFlags & VK_SHADER_STAGE_COMPUTE_BIT)
kk_push_constants(cmd, &cmd->state.cs.descriptors, pPushConstantsInfo);
}
void
kk_cmd_buffer_write_descriptor_buffer(struct kk_cmd_buffer *cmd,
struct kk_descriptor_state *desc,
size_t size, size_t offset)
{
assert(size + offset <= sizeof(desc->root.sets));
struct kk_ptr root_buffer = desc->root.root_buffer;
memcpy(root_buffer.cpu, (uint8_t *)desc->root.sets + offset, size);
}
void
kk_cmd_release_dynamic_ds_state(struct kk_cmd_buffer *cmd)
{
if (cmd->state.gfx.is_depth_stencil_dynamic &&
cmd->state.gfx.depth_stencil_state)
mtl_release(cmd->state.gfx.depth_stencil_state);
cmd->state.gfx.depth_stencil_state = NULL;
}
static VkResult
kk_cmd_buffer_alloc_bo(struct kk_cmd_buffer *cmd, struct kk_cmd_bo **bo_out)
{
VkResult result = kk_cmd_pool_alloc_bo(kk_cmd_buffer_pool(cmd), bo_out);
if (result != VK_SUCCESS)
return result;
list_addtail(&(*bo_out)->link, &cmd->uploader.bos);
return VK_SUCCESS;
}
struct kk_ptr
kk_pool_alloc(struct kk_cmd_buffer *cmd, uint32_t size_B, uint32_t alignment_B)
{
struct kk_device *dev = kk_cmd_buffer_device(cmd);
struct kk_uploader *uploader = &cmd->uploader;
/* Specially handle large allocations owned by the command buffer, e.g. used
* for statically allocated vertex output buffers with geometry shaders.
*/
if (size_B > KK_CMD_BO_SIZE) {
struct kk_bo *buffer = NULL;
const VkResult result =
kk_alloc_bo(dev, &cmd->vk.base, size_B, alignment_B, &buffer);
if (result != VK_SUCCESS) {
vk_command_buffer_set_error(&cmd->vk, result);
return (struct kk_ptr){0};
}
util_dynarray_append(&cmd->large_bos, buffer);
return (struct kk_ptr){
.gpu = buffer->gpu,
.cpu = buffer->cpu,
.buffer = buffer->map,
.offset = 0u,
};
}
assert(size_B <= KK_CMD_BO_SIZE);
assert(alignment_B > 0);
const uint32_t offset = align(uploader->offset, alignment_B);
assert(offset <= KK_CMD_BO_SIZE);
if (uploader->bo != NULL && size_B <= KK_CMD_BO_SIZE - offset) {
uploader->offset = offset + size_B;
return (struct kk_ptr){
.gpu = uploader->bo->gpu + offset,
.cpu = uploader->bo->cpu + offset,
.buffer = uploader->bo->map,
.offset = offset,
};
}
struct kk_cmd_bo *bo;
const VkResult result = kk_cmd_buffer_alloc_bo(cmd, &bo);
if (unlikely(result != VK_SUCCESS)) {
vk_command_buffer_set_error(&cmd->vk, result);
return (struct kk_ptr){0};
}
/* Pick whichever of the current upload BO and the new BO will have more
* room left to be the BO for the next upload. If our upload size is
* bigger than the old offset, we're better off burning the whole new
* upload BO on this one allocation and continuing on the current upload
* BO.
*/
if (uploader->bo == NULL || size_B < uploader->offset) {
uploader->bo = bo->bo;
uploader->offset = size_B;
}
return (struct kk_ptr){
.gpu = bo->bo->gpu,
.cpu = bo->bo->cpu,
.buffer = bo->bo->map,
.offset = 0u,
};
}
struct kk_ptr
kk_pool_upload(struct kk_cmd_buffer *cmd, const void *data, uint32_t size,
uint32_t alignment)
{
struct kk_ptr T = kk_pool_alloc(cmd, size, alignment);
if (unlikely(T.cpu == NULL))
return (struct kk_ptr){0};
memcpy(T.cpu, data, size);
return T;
}
uint64_t
kk_upload_descriptor_root(struct kk_cmd_buffer *cmd,
VkPipelineBindPoint bind_point)
{
struct kk_descriptor_state *desc = kk_get_descriptors_state(cmd, bind_point);
struct kk_root_descriptor_table *root = &desc->root;
struct kk_ptr root_gpu = kk_pool_upload(cmd, root, sizeof(*root), 8u);
if (unlikely(!root_gpu.gpu))
return 0u;
root->root_buffer = root_gpu;
desc->root_dirty = false;
return root_gpu.gpu;
}
void
kk_cmd_buffer_flush_push_descriptors(struct kk_cmd_buffer *cmd,
struct kk_descriptor_state *desc)
{
u_foreach_bit(set_idx, desc->push_dirty) {
struct kk_push_descriptor_set *push_set = desc->push[set_idx];
struct kk_ptr push_gpu = kk_pool_upload(
cmd, push_set->data, sizeof(push_set->data), KK_MIN_UBO_ALIGNMENT);
if (unlikely(!push_gpu.gpu))
return;
desc->root.sets[set_idx] = push_gpu.gpu;
desc->set_sizes[set_idx] = sizeof(push_set->data);
}
desc->root_dirty = true;
desc->push_dirty = 0;
}
void
kk_dispatch_precomp(struct kk_cmd_buffer *cmd, struct kk_grid grid,
bool pre_gfx, enum libkk_program idx, void *data,
size_t data_size)
{
struct kk_device *dev = kk_cmd_buffer_device(cmd);
struct kk_precompiled_shader *prog = &dev->precompiled_cache.shaders[idx];
mtl_compute_encoder *encoder =
pre_gfx ? kk_encoder_pre_gfx_encoder(cmd) : kk_compute_encoder(cmd);
struct kk_ptr data_gpu = kk_pool_upload(cmd, data, data_size, 8u);
if (unlikely(!data_gpu.gpu))
return;
mtl_compute_set_buffer(encoder, data_gpu.buffer, data_gpu.offset, 0);
mtl_compute_set_pipeline_state(encoder, prog->pipeline);
struct mtl_size local_size = {
.x = prog->info.workgroup_size[0],
.y = prog->info.workgroup_size[1],
.z = prog->info.workgroup_size[2],
};
if (grid.mode == KK_GRID_DIRECT)
mtl_dispatch_threads(encoder, grid.size, local_size);
else
mtl_dispatch_threadgroups_with_indirect_buffer(encoder, grid.indirect,
grid.offset, local_size);
}
void
kk_cmd_write(struct kk_cmd_buffer *cmd, struct libkk_imm_write write)
{
util_dynarray_append(&cmd->encoder->imm_writes, write);
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdPushDescriptorSetWithTemplate2KHR(
VkCommandBuffer commandBuffer, const VkPushDescriptorSetWithTemplateInfoKHR
*pPushDescriptorSetWithTemplateInfo)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
VK_FROM_HANDLE(vk_descriptor_update_template, template,
pPushDescriptorSetWithTemplateInfo->descriptorUpdateTemplate);
VK_FROM_HANDLE(vk_pipeline_layout, pipeline_layout,
pPushDescriptorSetWithTemplateInfo->layout);
struct kk_descriptor_state *desc =
kk_get_descriptors_state(cmd, template->bind_point);
struct kk_descriptor_set_layout *set_layout = vk_to_kk_descriptor_set_layout(
pipeline_layout->set_layouts[pPushDescriptorSetWithTemplateInfo->set]);
struct kk_push_descriptor_set *push_set = kk_cmd_push_descriptors(
cmd, desc, set_layout, pPushDescriptorSetWithTemplateInfo->set);
if (unlikely(push_set == NULL))
return;
kk_push_descriptor_set_update_template(
push_set, set_layout, template,
pPushDescriptorSetWithTemplateInfo->pData);
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdBeginConditionalRendering2EXT(
VkCommandBuffer commandBuffer,
const VkConditionalRenderingBeginInfo2EXT *pConditionalRenderingBegin)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
cmd->state.cond_render.address =
pConditionalRenderingBegin->addressRange.address;
cmd->state.cond_render.inverted = pConditionalRenderingBegin->flags &
VK_CONDITIONAL_RENDERING_INVERTED_BIT_EXT;
cmd->state.cond_render.enabled = true;
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdEndConditionalRenderingEXT(VkCommandBuffer commandBuffer)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
cmd->state.cond_render.enabled = false;
}
+356
View File
@@ -0,0 +1,356 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_CMD_BUFFER_H
#define KK_CMD_BUFFER_H 1
#include "kk_private.h"
#include "kk_descriptor_set.h"
#include "kk_image.h"
#include "kk_nir_lower_vbo.h"
#include "kk_shader.h"
#include "kosmickrisp/bridge/mtl_types.h"
#include "kosmickrisp/libkk/kk_query.h"
#include "util/u_dynarray.h"
#include "vk_command_buffer.h"
#include <stdio.h>
struct kk_query_pool;
struct kk_root_descriptor_table {
struct kk_ptr root_buffer;
union {
struct {
/* Vertex input state */
uint32_t buffer_strides[KK_MAX_VBUFS];
uint64_t attrib_base[KK_MAX_ATTRIBS];
uint32_t attrib_clamps[KK_MAX_ATTRIBS];
/* Mask of outputs flowing VS->TCS, VS->GS, or TES->GS . */
uint64_t vertex_outputs;
/* Address of vertex param buffer if geom/tess is used, else 0 */
uint64_t vertex_params;
/* Address of tessellation param buffer if tessellation used, else 0 */
uint64_t tess_params;
float blend_constant[4];
float clip_z_coeff;
uint32_t index_size;
uint64_t base_vertex_addr;
uint64_t base_instance_addr;
} draw;
struct {
uint32_t base_group[3];
} cs;
};
/* Client push constants */
uint8_t push[KK_MAX_PUSH_SIZE];
/* Descriptor set base addresses */
uint64_t sets[KK_MAX_SETS];
/* Dynamic buffer bindings */
struct kk_buffer_address dynamic_buffers[KK_MAX_DYNAMIC_BUFFERS];
/* Start index in dynamic_buffers where each set starts */
uint8_t set_dynamic_buffer_start[KK_MAX_SETS];
};
struct kk_descriptor_state {
bool root_dirty;
struct kk_root_descriptor_table root;
uint32_t set_sizes[KK_MAX_SETS];
struct kk_descriptor_set *sets[KK_MAX_SETS];
uint32_t push_dirty;
struct kk_push_descriptor_set *push[KK_MAX_SETS];
};
struct kk_per_draw_data {
uint32_t draw_id;
};
struct kk_attachment {
VkRenderingAttachmentFlagsKHR flags;
VkFormat vk_format;
struct kk_image_view *iview;
VkResolveModeFlagBits resolve_mode;
struct kk_image_view *resolve_iview;
/* Needed to track the value of storeOp in case we need to copy images for
* the DRM_FORMAT_MOD_LINEAR case */
VkAttachmentStoreOp store_op;
};
struct kk_rendering_state {
VkRenderingFlagBits flags;
VkRect2D area;
uint32_t layer_count;
uint32_t view_mask;
uint32_t samples;
uint32_t color_att_count;
struct kk_attachment color_att[KK_MAX_RTS];
struct kk_attachment depth_att;
struct kk_attachment stencil_att;
struct kk_attachment fsr_att;
};
/* Dirty tracking bits for state not tracked by vk_dynamic_graphics_state or
* shaders_dirty.
*/
enum kk_dirty {
KK_DIRTY_VB = BITFIELD_BIT(0),
KK_DIRTY_OCCLUSION = BITFIELD_BIT(1),
};
struct kk_graphics_state {
struct kk_rendering_state render;
struct kk_descriptor_state descriptors;
mtl_depth_stencil_state *depth_stencil_state;
mtl_render_pass_descriptor *render_pass_descriptor;
bool is_depth_stencil_dynamic;
bool is_cull_front_and_back;
bool is_ms_bresenham_lines;
bool need_to_start_render_pass;
enum kk_dirty dirty;
uint32_t pipeline_sample_count;
struct {
enum mtl_visibility_result_mode mode;
/* If enabled, index of the current occlusion query in the occlusion heap.
* There can only be one active at a time (hardware constraint).
*/
uint16_t index;
} occlusion;
/* Index buffer */
struct {
mtl_buffer *handle;
uint64_t buffer_size;
uint64_t range;
uint64_t offset;
uint8_t bytes_per_index;
} index;
/* Vertex buffers */
struct {
struct kk_addr_range addr_range[KK_MAX_VBUFS];
mtl_buffer *handles[KK_MAX_VBUFS];
} vb;
/* Tessellation state */
struct {
/* Grid buffer for when the draw is indirect */
struct kk_ptr indirect_ptr;
mtl_buffer *out_draws_buffer;
uint64_t out_draws_offset;
struct kk_tess_info info;
enum mesa_prim prim;
} tess;
/* Needed by vk_command_buffer::dynamic_graphics_state */
struct vk_vertex_input_state _dynamic_vi;
struct vk_sample_locations_state _dynamic_sl;
};
struct kk_compute_state {
struct kk_descriptor_state descriptors;
};
struct kk_conditional_rendering_state {
uint64_t address;
bool inverted;
bool enabled;
};
struct kk_encoder;
struct kk_uploader {
/** List of kk_cmd_bo */
struct list_head bos;
/* Current addresses */
struct kk_bo *bo;
uint32_t offset;
};
struct kk_cmd_buffer {
struct vk_command_buffer vk;
struct kk_encoder *encoder;
void *drawable;
struct {
struct kk_graphics_state gfx;
struct kk_compute_state cs;
struct kk_conditional_rendering_state cond_render;
struct kk_shader *shaders[MESA_SHADER_STAGES];
/* Only tracks graphics shaders since compute is always bound for now. */
uint32_t dirty_shaders;
} state;
struct kk_uploader uploader;
/* Owned large BOs */
struct util_dynarray large_bos;
/* Does the command buffer use the geometry heap? */
bool uses_heap;
};
VK_DEFINE_HANDLE_CASTS(kk_cmd_buffer, vk.base, VkCommandBuffer,
VK_OBJECT_TYPE_COMMAND_BUFFER)
extern const struct vk_command_buffer_ops kk_cmd_buffer_ops;
static inline struct kk_device *
kk_cmd_buffer_device(struct kk_cmd_buffer *cmd)
{
return (struct kk_device *)cmd->vk.base.device;
}
static inline struct kk_cmd_pool *
kk_cmd_buffer_pool(struct kk_cmd_buffer *cmd)
{
return (struct kk_cmd_pool *)cmd->vk.pool;
}
static inline struct kk_descriptor_state *
kk_get_descriptors_state(struct kk_cmd_buffer *cmd,
VkPipelineBindPoint bind_point)
{
switch (bind_point) {
case VK_PIPELINE_BIND_POINT_GRAPHICS:
return &cmd->state.gfx.descriptors;
case VK_PIPELINE_BIND_POINT_COMPUTE:
return &cmd->state.cs.descriptors;
default:
UNREACHABLE("Unhandled bind point");
}
};
void kk_cmd_release_resources(struct kk_device *dev, struct kk_cmd_buffer *cmd);
static void
kk_cmd_buffer_dirty_all_gfx(struct kk_cmd_buffer *cmd)
{
/* Ensure we flush all graphics state */
vk_dynamic_graphics_state_dirty_all(&cmd->vk.dynamic_graphics_state);
cmd->state.dirty_shaders = ~0u;
cmd->state.gfx.dirty = ~0u;
cmd->state.gfx.descriptors.root_dirty = true;
/* We just flushed out the heap use. If we want to use it again, we'll need
* to queue a free for it again.
*/
cmd->uses_heap = false;
}
void kk_cmd_release_dynamic_ds_state(struct kk_cmd_buffer *cmd);
mtl_depth_stencil_state *
kk_compile_depth_stencil_state(struct kk_device *device,
const struct vk_depth_stencil_state *ds,
bool has_depth, bool has_stencil);
void kk_meta_resolve_rendering(struct kk_cmd_buffer *cmd,
const VkRenderingInfo *pRenderingInfo);
void kk_cmd_buffer_write_descriptor_buffer(struct kk_cmd_buffer *cmd,
struct kk_descriptor_state *desc,
size_t size, size_t offset);
struct kk_ptr kk_pool_alloc(struct kk_cmd_buffer *cmd, uint32_t size,
uint32_t alignment);
struct kk_ptr kk_pool_upload(struct kk_cmd_buffer *cmd, const void *data,
uint32_t size, uint32_t alignment);
uint64_t kk_upload_descriptor_root(struct kk_cmd_buffer *cmd,
VkPipelineBindPoint bind_point);
void kk_cmd_buffer_flush_push_descriptors(struct kk_cmd_buffer *cmd,
struct kk_descriptor_state *desc);
enum kk_grid_mode {
KK_GRID_DIRECT = 0u,
KK_GRID_INDIRECT,
};
struct kk_grid {
enum kk_grid_mode mode;
union {
struct {
uint32_t offset;
mtl_buffer *indirect;
};
struct mtl_size size;
};
};
static struct kk_grid
kk_grid_3d(uint32_t x, uint32_t y, uint32_t z)
{
return (struct kk_grid){
.mode = KK_GRID_DIRECT,
.size = {x, y, z},
};
}
static struct kk_grid
kk_grid_2d(uint32_t x, uint32_t y)
{
return kk_grid_3d(x, y, 1u);
}
static struct kk_grid
kk_grid_1d(uint32_t x)
{
return kk_grid_3d(x, 1u, 1u);
}
static struct kk_grid
kk_grid_indirect(mtl_buffer *indirect, uint32_t offset)
{
return (struct kk_grid){
.mode = KK_GRID_INDIRECT,
.indirect = indirect,
.offset = offset,
};
}
static bool
kk_grid_is_indirect(struct kk_grid grid)
{
return grid.mode == KK_GRID_INDIRECT;
}
void kk_dispatch_precomp(struct kk_cmd_buffer *cmd, struct kk_grid grid,
bool pre_gfx, enum libkk_program idx, void *data,
size_t data_size);
#define MESA_DISPATCH_PRECOMP kk_dispatch_precomp
void kk_cmd_write(struct kk_cmd_buffer *cmd, struct libkk_imm_write write);
#endif
+174
View File
@@ -0,0 +1,174 @@
/*
* Copyright 2024 Valve Corporation
* Copyright 2024 Alyssa Rosenzweig
* Copyright 2022-2023 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_cmd_buffer.h"
#include "kk_device.h"
#include "kk_entrypoints.h"
#include "kk_image.h"
#include "kk_image_view.h"
#include "kk_physical_device.h"
#include "vk_format.h"
#include "vk_meta.h"
static VkImageViewType
render_view_type(VkImageType image_type, unsigned layer_count)
{
switch (image_type) {
case VK_IMAGE_TYPE_1D:
return layer_count == 1 ? VK_IMAGE_VIEW_TYPE_1D
: VK_IMAGE_VIEW_TYPE_1D_ARRAY;
case VK_IMAGE_TYPE_2D:
return layer_count == 1 ? VK_IMAGE_VIEW_TYPE_2D
: VK_IMAGE_VIEW_TYPE_2D_ARRAY;
case VK_IMAGE_TYPE_3D:
return VK_IMAGE_VIEW_TYPE_3D;
default:
UNREACHABLE("Invalid image type");
}
}
static void
clear_image(struct kk_cmd_buffer *cmd, struct kk_image *image,
VkImageLayout image_layout, VkFormat format,
const VkClearValue *clear_value, uint32_t range_count,
const VkImageSubresourceRange *ranges)
{
struct kk_device *dev = kk_cmd_buffer_device(cmd);
ASSERTED VkResult result;
for (uint32_t r = 0; r < range_count; r++) {
const uint32_t level_count =
vk_image_subresource_level_count(&image->vk, &ranges[r]);
for (uint32_t l = 0; l < level_count; l++) {
const uint32_t level = ranges[r].baseMipLevel + l;
const VkExtent3D level_extent =
vk_image_mip_level_extent(&image->vk, level);
uint32_t base_array_layer, layer_count;
if (image->vk.image_type == VK_IMAGE_TYPE_3D) {
base_array_layer = 0;
layer_count = level_extent.depth;
} else {
base_array_layer = ranges[r].baseArrayLayer;
layer_count =
vk_image_subresource_layer_count(&image->vk, &ranges[r]);
}
const VkImageViewUsageCreateInfo view_usage_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
.usage = (ranges[r].aspectMask & VK_IMAGE_ASPECT_COLOR_BIT)
? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
: VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
};
const VkImageViewCreateInfo view_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.flags = VK_IMAGE_VIEW_CREATE_DRIVER_INTERNAL_BIT_MESA,
.pNext = &view_usage_info,
.image = kk_image_to_handle(image),
.viewType = render_view_type(image->vk.image_type, layer_count),
.format = format,
.subresourceRange =
{
.aspectMask = image->vk.aspects,
.baseMipLevel = level,
.levelCount = 1,
.baseArrayLayer = base_array_layer,
.layerCount = layer_count,
},
};
/* We use vk_meta_create_image_view here for lifetime managemnt */
VkImageView view;
result =
vk_meta_create_image_view(&cmd->vk, &dev->meta, &view_info, &view);
assert(result == VK_SUCCESS);
VkRenderingInfo render = {
.sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
.renderArea =
{
.offset = {0, 0},
.extent = {level_extent.width, level_extent.height},
},
.layerCount = layer_count,
};
VkRenderingAttachmentInfo vk_att = {
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = view,
.imageLayout = image_layout,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.clearValue = *clear_value,
};
if (ranges[r].aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
render.colorAttachmentCount = 1;
render.pColorAttachments = &vk_att;
}
if (ranges[r].aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT)
render.pDepthAttachment = &vk_att;
if (ranges[r].aspectMask & VK_IMAGE_ASPECT_STENCIL_BIT)
render.pStencilAttachment = &vk_att;
kk_CmdBeginRendering(kk_cmd_buffer_to_handle(cmd), &render);
static const VkRenderingEndInfoKHR end_info = {
.sType = VK_STRUCTURE_TYPE_RENDERING_END_INFO_KHR,
.pNext = NULL,
};
kk_CmdEndRendering2KHR(kk_cmd_buffer_to_handle(cmd), &end_info);
}
}
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdClearColorImage(VkCommandBuffer commandBuffer, VkImage _image,
VkImageLayout imageLayout,
const VkClearColorValue *pColor, uint32_t rangeCount,
const VkImageSubresourceRange *pRanges)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
VK_FROM_HANDLE(kk_image, image, _image);
VkClearValue clear_value = {
.color = *pColor,
};
VkFormat vk_format = image->vk.format;
if (vk_format == VK_FORMAT_R64_UINT || vk_format == VK_FORMAT_R64_SINT)
vk_format = VK_FORMAT_R32G32_UINT;
enum pipe_format p_format = vk_format_to_pipe_format(vk_format);
assert(p_format != PIPE_FORMAT_NONE);
clear_image(cmd, image, imageLayout, vk_format, &clear_value, rangeCount,
pRanges);
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdClearDepthStencilImage(VkCommandBuffer commandBuffer, VkImage _image,
VkImageLayout imageLayout,
const VkClearDepthStencilValue *pDepthStencil,
uint32_t rangeCount,
const VkImageSubresourceRange *pRanges)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
VK_FROM_HANDLE(kk_image, image, _image);
const VkClearValue clear_value = {
.depthStencil = *pDepthStencil,
};
clear_image(cmd, image, imageLayout, image->vk.format, &clear_value,
rangeCount, pRanges);
}
+398
View File
@@ -0,0 +1,398 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_cmd_buffer.h"
#include "kk_bo.h"
#include "kk_buffer.h"
#include "kk_device.h"
#include "kk_encoder.h"
#include "kk_entrypoints.h"
#include "kk_physical_device.h"
#include "kosmickrisp/bridge/mtl_bridge.h"
#include "kosmickrisp/bridge/vk_to_mtl_map.h"
#include "util/format/u_format.h"
VKAPI_ATTR void VKAPI_CALL
kk_CmdCopyBuffer2(VkCommandBuffer commandBuffer,
const VkCopyBufferInfo2 *pCopyBufferInfo)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
VK_FROM_HANDLE(kk_buffer, src, pCopyBufferInfo->srcBuffer);
VK_FROM_HANDLE(kk_buffer, dst, pCopyBufferInfo->dstBuffer);
mtl_blit_encoder *blit = kk_blit_encoder(cmd);
for (uint32_t i = 0; i < pCopyBufferInfo->regionCount; i++) {
const VkBufferCopy2 *region = &pCopyBufferInfo->pRegions[i];
mtl_copy_from_buffer_to_buffer(blit, src->mtl_handle, region->srcOffset,
dst->mtl_handle, region->dstOffset,
region->size);
}
}
struct kk_buffer_image_copy_info {
struct mtl_buffer_image_copy mtl_data;
size_t buffer_slice_size_B;
};
static struct kk_buffer_image_copy_info
vk_buffer_image_copy_to_mtl_buffer_image_copy(
const VkBufferImageCopy2 *region, const struct kk_image_plane *plane)
{
struct kk_buffer_image_copy_info copy;
enum pipe_format p_format = plane->layout.format.pipe;
if (region->imageSubresource.aspectMask == VK_IMAGE_ASPECT_DEPTH_BIT) {
copy.mtl_data.options = MTL_BLIT_OPTION_DEPTH_FROM_DEPTH_STENCIL;
p_format = util_format_get_depth_only(p_format);
} else if (region->imageSubresource.aspectMask ==
VK_IMAGE_ASPECT_STENCIL_BIT) {
copy.mtl_data.options = MTL_BLIT_OPTION_STENCIL_FROM_DEPTH_STENCIL;
p_format = PIPE_FORMAT_S8_UINT;
} else
copy.mtl_data.options = MTL_BLIT_OPTION_NONE;
const uint32_t buffer_width = region->bufferRowLength
? region->bufferRowLength
: region->imageExtent.width;
const uint32_t buffer_height = region->bufferImageHeight
? region->bufferImageHeight
: region->imageExtent.height;
const uint32_t buffer_stride_B =
util_format_get_stride(p_format, buffer_width);
const uint32_t buffer_size_2d_B =
util_format_get_2d_size(p_format, buffer_stride_B, buffer_height);
/* Metal requires this value to be 0 for 2D images, otherwise the number of
* bytes between each 2D image of a 3D texture */
copy.mtl_data.buffer_2d_image_size_B =
plane->layout.depth_px == 1u ? 0u : buffer_size_2d_B;
copy.mtl_data.buffer_stride_B = buffer_stride_B;
copy.mtl_data.image_size = vk_extent_3d_to_mtl_size(&region->imageExtent);
copy.mtl_data.image_origin =
vk_offset_3d_to_mtl_origin(&region->imageOffset);
copy.mtl_data.image_level = region->imageSubresource.mipLevel;
copy.buffer_slice_size_B = buffer_size_2d_B;
return copy;
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdCopyBufferToImage2(VkCommandBuffer commandBuffer,
const VkCopyBufferToImageInfo2 *pCopyBufferToImageInfo)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
VK_FROM_HANDLE(kk_buffer, buffer, pCopyBufferToImageInfo->srcBuffer);
VK_FROM_HANDLE(kk_image, image, pCopyBufferToImageInfo->dstImage);
mtl_blit_encoder *blit = kk_blit_encoder(cmd);
for (int r = 0; r < pCopyBufferToImageInfo->regionCount; r++) {
const VkBufferImageCopy2 *region = &pCopyBufferToImageInfo->pRegions[r];
const uint8_t plane_index = kk_image_memory_aspects_to_plane(
image, region->imageSubresource.aspectMask);
struct kk_image_plane *plane = &image->planes[plane_index];
struct kk_buffer_image_copy_info info =
vk_buffer_image_copy_to_mtl_buffer_image_copy(region, plane);
info.mtl_data.buffer = buffer->mtl_handle;
info.mtl_data.image = plane->mtl_handle;
size_t buffer_offset = region->bufferOffset;
kk_foreach_slice(slice, image, imageSubresource)
{
info.mtl_data.image_slice = slice;
info.mtl_data.buffer_offset_B = buffer_offset;
mtl_copy_from_buffer_to_texture(blit, &info.mtl_data);
buffer_offset += info.buffer_slice_size_B;
}
}
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdCopyImageToBuffer2(VkCommandBuffer commandBuffer,
const VkCopyImageToBufferInfo2 *pCopyImageToBufferInfo)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
VK_FROM_HANDLE(kk_image, image, pCopyImageToBufferInfo->srcImage);
VK_FROM_HANDLE(kk_buffer, buffer, pCopyImageToBufferInfo->dstBuffer);
mtl_blit_encoder *blit = kk_blit_encoder(cmd);
for (unsigned r = 0; r < pCopyImageToBufferInfo->regionCount; r++) {
const VkBufferImageCopy2 *region = &pCopyImageToBufferInfo->pRegions[r];
const uint8_t plane_index = kk_image_memory_aspects_to_plane(
image, region->imageSubresource.aspectMask);
struct kk_image_plane *plane = &image->planes[plane_index];
struct kk_buffer_image_copy_info info =
vk_buffer_image_copy_to_mtl_buffer_image_copy(region, plane);
info.mtl_data.buffer = buffer->mtl_handle;
info.mtl_data.image = plane->mtl_handle;
size_t buffer_offset = region->bufferOffset;
kk_foreach_slice(slice, image, imageSubresource)
{
info.mtl_data.image_slice = slice;
info.mtl_data.buffer_offset_B = buffer_offset;
mtl_copy_from_texture_to_buffer(blit, &info.mtl_data);
buffer_offset += info.buffer_slice_size_B;
}
}
}
/* Copies images by doing a texture->buffer->texture transfer. Returns the new
* buffer offset for subsequent copies. */
static size_t
copy_through_buffer(struct kk_cmd_buffer *cmd, struct kk_image *src,
uint32_t src_index, struct kk_image *dst,
uint32_t dst_index, mtl_buffer *buffer,
size_t buffer_offset, const VkImageCopy2 *region)
{
struct kk_image_plane *src_plane = &src->planes[src_index];
struct kk_image_plane *dst_plane = &dst->planes[dst_index];
/* Handle depth/stencil for copies to/from compatible color formats */
enum pipe_format src_format = src_plane->layout.format.pipe;
enum mtl_blit_options src_options = MTL_BLIT_OPTION_NONE;
if (region->srcSubresource.aspectMask == VK_IMAGE_ASPECT_DEPTH_BIT) {
src_format = util_format_get_depth_only(src_format);
src_options = MTL_BLIT_OPTION_DEPTH_FROM_DEPTH_STENCIL;
} else if (region->srcSubresource.aspectMask ==
VK_IMAGE_ASPECT_STENCIL_BIT) {
src_format = PIPE_FORMAT_S8_UINT;
src_options = MTL_BLIT_OPTION_STENCIL_FROM_DEPTH_STENCIL;
}
enum pipe_format dst_format = dst_plane->layout.format.pipe;
enum mtl_blit_options dst_options = MTL_BLIT_OPTION_NONE;
if (region->dstSubresource.aspectMask == VK_IMAGE_ASPECT_DEPTH_BIT) {
dst_format = util_format_get_depth_only(dst_format);
dst_options = MTL_BLIT_OPTION_DEPTH_FROM_DEPTH_STENCIL;
} else if (region->dstSubresource.aspectMask ==
VK_IMAGE_ASPECT_STENCIL_BIT) {
dst_format = PIPE_FORMAT_S8_UINT;
dst_options = MTL_BLIT_OPTION_STENCIL_FROM_DEPTH_STENCIL;
}
bool is_src_compressed = util_format_is_compressed(src_format);
bool is_dst_compressed = util_format_is_compressed(dst_format);
mtl_blit_encoder *blit = kk_blit_encoder(cmd);
const uint32_t buffer_stride_B =
util_format_get_stride(src_format, region->extent.width);
const uint32_t buffer_size_2d_B = util_format_get_2d_size(
src_format, buffer_stride_B, region->extent.height);
struct kk_buffer_image_copy_info info;
/* Metal requires this value to be 0 for 2D images, otherwise the number
* of bytes between each 2D image of a 3D texture */
info.mtl_data.buffer_2d_image_size_B =
src_plane->layout.depth_px == 1u ? 0u : buffer_size_2d_B;
info.mtl_data.buffer_stride_B = buffer_stride_B;
info.mtl_data.buffer = buffer;
info.buffer_slice_size_B = buffer_size_2d_B;
struct mtl_size src_size = vk_extent_3d_to_mtl_size(&region->extent);
struct mtl_size dst_size = vk_extent_3d_to_mtl_size(&region->extent);
/* Need to adjust size to block dimensions */
if (is_src_compressed) {
dst_size.x = util_format_get_nblocksx(src_format, dst_size.x);
dst_size.y = util_format_get_nblocksy(src_format, dst_size.y);
dst_size.z = util_format_get_nblocksz(src_format, dst_size.z);
}
if (is_dst_compressed) {
dst_size.x *= util_format_get_blockwidth(dst_format);
dst_size.y *= util_format_get_blockheight(dst_format);
dst_size.z *= util_format_get_blockdepth(dst_format);
}
/* After adjusting for compression, sanitize destination extents for 1D/2D */
if (dst_plane->layout.height_px == 1)
dst_size.y = 1;
if (dst_plane->layout.depth_px == 1)
dst_size.z = 1;
struct mtl_origin src_origin =
vk_offset_3d_to_mtl_origin(&region->srcOffset);
struct mtl_origin dst_origin =
vk_offset_3d_to_mtl_origin(&region->dstOffset);
/* Texture->Buffer->Texture */
// TODO_KOSMICKRISP We don't handle 3D to 2D array nor vice-versa in this
// path. Unsure if it's even needed, can compressed textures be 3D?
for (uint32_t slice_idx = 0;
slice_idx <
vk_image_subresource_layer_count(&src->vk, &region->srcSubresource);
++slice_idx) {
info.mtl_data.image = src_plane->mtl_handle;
info.mtl_data.image_size = src_size;
info.mtl_data.image_origin = src_origin;
info.mtl_data.image_slice =
region->srcSubresource.baseArrayLayer + slice_idx;
info.mtl_data.image_level = region->srcSubresource.mipLevel;
info.mtl_data.buffer_offset_B = buffer_offset;
info.mtl_data.options = src_options;
mtl_copy_from_texture_to_buffer(blit, &info.mtl_data);
info.mtl_data.image = dst_plane->mtl_handle;
info.mtl_data.image_size = dst_size;
info.mtl_data.image_origin = dst_origin;
info.mtl_data.image_slice =
region->dstSubresource.baseArrayLayer + slice_idx;
info.mtl_data.image_level = region->dstSubresource.mipLevel;
info.mtl_data.options = dst_options;
mtl_copy_from_buffer_to_texture(blit, &info.mtl_data);
buffer_offset += info.buffer_slice_size_B;
}
return buffer_offset;
}
static bool
can_do_image_to_image_copy(struct kk_image *src, uint32_t src_index,
struct kk_image *dst, uint32_t dst_index)
{
struct kk_image_plane *src_plane = &src->planes[src_index];
struct kk_image_plane *dst_plane = &dst->planes[dst_index];
enum pipe_format src_format = src_plane->layout.format.pipe;
enum pipe_format dst_format = dst_plane->layout.format.pipe;
/* Metal validation fails for image-to-image copies if the dimension is
* relevant to the image type and not a multiple of the block dimension.
* Since 1D textures are emulated as 2D, this causes problems with
* compressed 1D textures.
*
* However, Metal documentation also states:
*
* If the block extends outside the bounds of the texture, clamp
* sourceSize to the edge of the texture.
*
* So this may be a bug in the validation layer. Routing to the buffer copy
* path avoids it.
*/
if (src->vk.image_type == VK_IMAGE_TYPE_1D &&
util_format_is_compressed(src_format))
return false;
return src_format == dst_format && src_plane->layout.sample_count_sa ==
dst_plane->layout.sample_count_sa;
}
/* Copies images through Metal's texture->texture copy mechanism */
static void
copy_image(struct kk_cmd_buffer *cmd, struct kk_image *src, uint32_t src_index,
struct kk_image *dst, uint32_t dst_index, const VkImageCopy2 *region)
{
mtl_blit_encoder *blit = kk_blit_encoder(cmd);
struct kk_image_plane *src_plane = &src->planes[src_index];
struct kk_image_plane *dst_plane = &dst->planes[dst_index];
/* From the Vulkan 1.3.217 spec:
*
* "When copying between compressed and uncompressed formats the
* extent members represent the texel dimensions of the source image
* and not the destination."
*/
const VkExtent3D extent_px =
vk_image_sanitize_extent(&src->vk, region->extent);
size_t src_slice = region->srcSubresource.baseArrayLayer;
size_t src_level = region->srcSubresource.mipLevel;
struct mtl_origin src_origin =
vk_offset_3d_to_mtl_origin(&region->srcOffset);
struct mtl_size size = {.x = extent_px.width,
.y = extent_px.height,
.z = extent_px.depth};
size_t dst_slice = region->dstSubresource.baseArrayLayer;
size_t dst_level = region->dstSubresource.mipLevel;
struct mtl_origin dst_origin =
vk_offset_3d_to_mtl_origin(&region->dstOffset);
/* When copying 3D to 2D layered or vice-versa, we need to change the 3D
* size to 2D and iterate on the layer count of the 2D image (which is the
* same as the depth of the 3D) and adjust origin and slice accordingly */
uint32_t layer_count =
vk_image_subresource_layer_count(&src->vk, &region->srcSubresource);
const uint32_t dst_layer_count =
vk_image_subresource_layer_count(&dst->vk, &region->dstSubresource);
size_t *src_increase = &src_slice;
size_t *dst_increase = &dst_slice;
if (layer_count < dst_layer_count) { /* 3D to 2D layered */
layer_count = dst_layer_count;
src_increase = &src_origin.z;
size.z = 1u;
} else if (dst_layer_count < layer_count) { /* 2D layered to 3D */
dst_increase = &dst_origin.z;
size.z = 1u;
}
for (uint32_t l = 0; l < layer_count;
++l, ++(*src_increase), ++(*dst_increase)) {
mtl_copy_from_texture_to_texture(
blit, src_plane->mtl_handle, src_slice, src_level, src_origin, size,
dst_plane->mtl_handle, dst_slice, dst_level, dst_origin);
}
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdCopyImage2(VkCommandBuffer commandBuffer,
const VkCopyImageInfo2 *pCopyImageInfo)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
VK_FROM_HANDLE(kk_image, src, pCopyImageInfo->srcImage);
VK_FROM_HANDLE(kk_image, dst, pCopyImageInfo->dstImage);
size_t buffer_size = 0u;
/* Copy as much as we can through Metal's image to image copy that only
* supports same format and sample count while getting the required buffer
* size for image->buffer->image copy. */
for (uint32_t i = 0u; i < pCopyImageInfo->regionCount; ++i) {
const VkImageCopy2 *region = &pCopyImageInfo->pRegions[i];
uint8_t src_index =
kk_image_aspects_to_plane(src, region->srcSubresource.aspectMask);
uint8_t dst_index =
kk_image_aspects_to_plane(dst, region->dstSubresource.aspectMask);
if (can_do_image_to_image_copy(src, src_index, dst, dst_index))
copy_image(cmd, src, src_index, dst, dst_index, region);
else {
struct kk_image_plane *src_plane = &src->planes[src_index];
enum pipe_format src_format = src_plane->layout.format.pipe;
const uint32_t buffer_stride_B =
util_format_get_stride(src_format, region->extent.width);
const uint32_t buffer_size_2d_B = util_format_get_2d_size(
src_format, buffer_stride_B, region->extent.height);
const uint32_t layer_count =
vk_image_subresource_layer_count(&src->vk, &region->srcSubresource);
buffer_size += buffer_size_2d_B * layer_count;
}
}
/* Copy source image to buffer then to the destination image for those
* regions that image to image was not possible. */
if (buffer_size) {
struct kk_ptr buf = kk_pool_alloc(cmd, buffer_size, 8);
if (unlikely(!buf.gpu))
return;
size_t buffer_offset = buf.offset;
for (uint32_t i = 0u; i < pCopyImageInfo->regionCount; ++i) {
const VkImageCopy2 *region = &pCopyImageInfo->pRegions[i];
uint8_t src_index =
kk_image_aspects_to_plane(src, region->srcSubresource.aspectMask);
uint8_t dst_index =
kk_image_aspects_to_plane(dst, region->dstSubresource.aspectMask);
if (!can_do_image_to_image_copy(src, src_index, dst, dst_index))
buffer_offset =
copy_through_buffer(cmd, src, src_index, dst, dst_index,
buf.buffer, buffer_offset, region);
}
}
}
+155
View File
@@ -0,0 +1,155 @@
/*
* Copyright © 2025 LunarG, Inc
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "vulkan/vulkan_core.h"
#include "kk_buffer.h"
#include "kk_cmd_buffer.h"
#include "kk_descriptor_set_layout.h"
#include "kk_device.h"
#include "kk_encoder.h"
#include "kk_entrypoints.h"
#include "kk_shader.h"
#include "kosmickrisp/bridge/mtl_bridge.h"
#include "vk_common_entrypoints.h"
static void
kk_flush_compute_state(struct kk_cmd_buffer *cmd)
{
mtl_compute_encoder *enc = kk_compute_encoder(cmd);
// Fill Metal argument buffer with descriptor set addresses
struct kk_descriptor_state *desc = &cmd->state.cs.descriptors;
if (desc->push_dirty)
kk_cmd_buffer_flush_push_descriptors(cmd, desc);
if (desc->root_dirty)
kk_upload_descriptor_root(cmd, VK_PIPELINE_BIND_POINT_COMPUTE);
struct kk_ptr root_buffer = desc->root.root_buffer;
if (root_buffer.gpu)
mtl_compute_set_buffer(enc, root_buffer.buffer, root_buffer.offset, 0);
mtl_compute_set_pipeline_state(
enc, cmd->state.shaders[MESA_SHADER_COMPUTE]->pipeline.cs);
}
static void
kk_predicate_compute(struct kk_cmd_buffer *cmd, uint64_t indirect_addr_out,
uint64_t indirect_addr_in)
{
uint64_t cond_addr = cmd->state.cond_render.address;
/* TODO_KOSMICKRISP: This can be accomplished more efficiently using device
* generated commands, constructing an indirect command buffer on the GPU
* which only contains the commands to run if the condition is true. For the
* time being, we apply predicates by zeroing out disabled indirect data */
struct kk_grid grid = kk_grid_1d(1u);
if (cmd->state.cond_render.inverted) {
libkk_predicate_indirect_eq_zero(cmd, grid, false, indirect_addr_out,
indirect_addr_in, cond_addr, 3u, 3u);
} else {
libkk_predicate_indirect_neq_zero(cmd, grid, false, indirect_addr_out,
indirect_addr_in, cond_addr, 3u, 3u);
}
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdDispatchBase(VkCommandBuffer commandBuffer, uint32_t baseGroupX,
uint32_t baseGroupY, uint32_t baseGroupZ,
uint32_t groupCountX, uint32_t groupCountY,
uint32_t groupCountZ)
{
/* Metal validation dislikes empty disptaches */
if (groupCountX * groupCountY * groupCountZ == 0)
return;
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
struct kk_descriptor_state *desc = &cmd->state.cs.descriptors;
desc->root_dirty |= desc->root.cs.base_group[0] != baseGroupX;
desc->root_dirty |= desc->root.cs.base_group[1] != baseGroupY;
desc->root_dirty |= desc->root.cs.base_group[2] != baseGroupZ;
desc->root.cs.base_group[0] = baseGroupX;
desc->root.cs.base_group[1] = baseGroupY;
desc->root.cs.base_group[2] = baseGroupZ;
struct kk_shader *cs = cmd->state.shaders[MESA_SHADER_COMPUTE];
struct mtl_size local_size = cs->info.cs.local_size;
mtl_compute_encoder *enc = kk_compute_encoder(cmd);
if (cmd->state.cond_render.enabled) {
/* Convert to indirect for predication */
VkDispatchIndirectCommand indirect = {
.x = groupCountX,
.y = groupCountY,
.z = groupCountZ,
};
struct kk_ptr patched =
kk_pool_upload(cmd, &indirect, sizeof(indirect), 4u);
if (unlikely(patched.gpu == 0)) {
return;
}
kk_predicate_compute(cmd, patched.gpu, patched.gpu);
/* Flush compute state after predication dispatch */
kk_flush_compute_state(cmd);
mtl_dispatch_threadgroups_with_indirect_buffer(
enc, patched.buffer, patched.offset, local_size);
} else {
struct mtl_size grid_size = {
.x = groupCountX * local_size.x,
.y = groupCountY * local_size.y,
.z = groupCountZ * local_size.z,
};
kk_flush_compute_state(cmd);
mtl_dispatch_threads(enc, grid_size, local_size);
}
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdDispatchIndirect(VkCommandBuffer commandBuffer, VkBuffer _buffer,
VkDeviceSize offset)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
VK_FROM_HANDLE(kk_buffer, buffer, _buffer);
struct kk_descriptor_state *desc = &cmd->state.cs.descriptors;
desc->root_dirty |= desc->root.cs.base_group[0] != 0;
desc->root_dirty |= desc->root.cs.base_group[1] != 0;
desc->root_dirty |= desc->root.cs.base_group[2] != 0;
desc->root.cs.base_group[0] = 0;
desc->root.cs.base_group[1] = 0;
desc->root.cs.base_group[2] = 0;
struct kk_shader *cs = cmd->state.shaders[MESA_SHADER_COMPUTE];
struct mtl_size local_size = cs->info.cs.local_size;
mtl_compute_encoder *enc = kk_compute_encoder(cmd);
if (cmd->state.cond_render.enabled) {
struct kk_ptr patched =
kk_pool_alloc(cmd, sizeof(VkDispatchIndirectCommand), 4u);
if (unlikely(patched.gpu == 0)) {
return;
}
kk_predicate_compute(cmd, patched.gpu,
vk_buffer_address(&buffer->vk, offset));
/* Flush compute state after predication dispatch */
kk_flush_compute_state(cmd);
mtl_dispatch_threadgroups_with_indirect_buffer(
enc, patched.buffer, patched.offset, local_size);
} else {
kk_flush_compute_state(cmd);
mtl_dispatch_threadgroups_with_indirect_buffer(enc, buffer->mtl_handle,
offset, local_size);
}
}
File diff suppressed because it is too large. Load diff
+316
View File
@@ -0,0 +1,316 @@
/*
* Copyright 2024 Valve Corporation
* Copyright 2024 Alyssa Rosenzweig
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_private.h"
#include "kk_buffer.h"
#include "kk_cmd_buffer.h"
#include "kk_encoder.h"
#include "kosmickrisp/bridge/mtl_bridge.h"
#include "kk_entrypoints.h"
static VkResult
kk_cmd_bind_map_buffer(struct vk_command_buffer *vk_cmd,
struct vk_meta_device *meta, VkBuffer _buffer,
void **map_out)
{
struct kk_cmd_buffer *cmd = container_of(vk_cmd, struct kk_cmd_buffer, vk);
VK_FROM_HANDLE(kk_buffer, buffer, _buffer);
assert(buffer->vk.size < UINT_MAX);
struct kk_ptr buf = kk_pool_alloc(cmd, buffer->vk.size, 16u);
if (unlikely(!buf.gpu))
return VK_ERROR_OUT_OF_POOL_MEMORY;
/* Need to retain since VkBuffers release the mtl_handle too */
mtl_retain(buf.buffer);
buffer->mtl_handle = buf.buffer;
buffer->vk.device_address = buf.gpu;
*map_out = buf.cpu;
return VK_SUCCESS;
}
VkResult
kk_device_init_meta(struct kk_device *dev)
{
VkResult result = vk_meta_device_init(&dev->vk, &dev->meta);
if (result != VK_SUCCESS)
return result;
dev->meta.use_gs_for_layer = false;
dev->meta.use_stencil_export = true;
dev->meta.use_rect_list_pipeline = true;
dev->meta.cmd_bind_map_buffer = kk_cmd_bind_map_buffer;
dev->meta.max_bind_map_buffer_size_B = 64 * 1024;
for (unsigned i = 0; i < VK_META_BUFFER_CHUNK_SIZE_COUNT; ++i) {
dev->meta.buffer_access.optimal_wg_size[i] = 64;
}
return VK_SUCCESS;
}
void
kk_device_finish_meta(struct kk_device *dev)
{
vk_meta_device_finish(&dev->vk, &dev->meta);
}
struct kk_meta_save {
struct vk_vertex_input_state _dynamic_vi;
struct vk_sample_locations_state _dynamic_sl;
struct vk_dynamic_graphics_state dynamic;
struct kk_shader *shaders[MESA_SHADER_STAGES];
struct {
union {
struct {
mtl_render_pipeline_state *ps;
mtl_depth_stencil_state *ds;
enum mtl_visibility_result_mode occlusion;
bool is_ds_dynamic;
} gfx;
};
} pipeline;
struct kk_conditional_rendering_state cond_render;
struct kk_descriptor_set *desc0;
struct kk_push_descriptor_set *push_desc0;
mtl_buffer *vb0_handle;
struct kk_addr_range vb0;
struct kk_buffer_address desc0_set_addr;
bool has_push_desc0;
uint8_t push[KK_MAX_PUSH_SIZE];
};
static void
kk_meta_begin(struct kk_cmd_buffer *cmd, struct kk_meta_save *save,
VkPipelineBindPoint bind_point)
{
struct kk_descriptor_state *desc = kk_get_descriptors_state(cmd, bind_point);
if (bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS) {
save->dynamic = cmd->vk.dynamic_graphics_state;
save->_dynamic_vi = cmd->state.gfx._dynamic_vi;
save->_dynamic_sl = cmd->state.gfx._dynamic_sl;
for (uint32_t stage = 0u; stage < MESA_SHADER_COMPUTE; ++stage) {
save->shaders[stage] = cmd->state.shaders[stage];
}
save->pipeline.gfx.ds = cmd->state.gfx.depth_stencil_state;
save->pipeline.gfx.occlusion = cmd->state.gfx.occlusion.mode;
save->pipeline.gfx.is_ds_dynamic =
cmd->state.gfx.is_depth_stencil_dynamic;
cmd->state.gfx.is_depth_stencil_dynamic = false;
cmd->state.gfx.depth_stencil_state = NULL;
cmd->state.gfx.occlusion.mode = MTL_VISIBILITY_RESULT_MODE_DISABLED;
cmd->state.gfx.dirty |= KK_DIRTY_OCCLUSION;
desc->root_dirty = true;
} else {
save->shaders[MESA_SHADER_COMPUTE] =
cmd->state.shaders[MESA_SHADER_COMPUTE];
}
save->cond_render = cmd->state.cond_render;
cmd->state.cond_render.enabled = false;
save->vb0_handle = cmd->state.gfx.vb.handles[0];
save->vb0 = cmd->state.gfx.vb.addr_range[0];
save->desc0 = desc->sets[0];
save->has_push_desc0 = desc->push[0];
if (save->has_push_desc0)
save->push_desc0 = desc->push[0];
static_assert(sizeof(save->push) == sizeof(desc->root.push),
"Size mismatch for push in meta_save");
memcpy(save->push, desc->root.push, sizeof(save->push));
}
static void
kk_meta_end(struct kk_cmd_buffer *cmd, struct kk_meta_save *save,
VkPipelineBindPoint bind_point)
{
struct kk_descriptor_state *desc = kk_get_descriptors_state(cmd, bind_point);
desc->root_dirty = true;
if (save->desc0) {
desc->sets[0] = save->desc0;
desc->root.sets[0] = save->desc0->addr;
desc->set_sizes[0] = save->desc0->size;
desc->push_dirty &= ~BITFIELD_BIT(0);
} else if (save->has_push_desc0) {
desc->push[0] = save->push_desc0;
desc->push_dirty |= BITFIELD_BIT(0);
}
if (bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS) {
/* Restore the dynamic state */
assert(save->dynamic.vi == &cmd->state.gfx._dynamic_vi);
assert(save->dynamic.ms.sample_locations == &cmd->state.gfx._dynamic_sl);
cmd->vk.dynamic_graphics_state = save->dynamic;
cmd->state.gfx._dynamic_vi = save->_dynamic_vi;
cmd->state.gfx._dynamic_sl = save->_dynamic_sl;
memcpy(cmd->vk.dynamic_graphics_state.dirty,
cmd->vk.dynamic_graphics_state.set,
sizeof(cmd->vk.dynamic_graphics_state.set));
for (uint32_t stage = 0u; stage < MESA_SHADER_COMPUTE; ++stage) {
cmd->state.shaders[stage] = save->shaders[stage];
}
cmd->state.dirty_shaders |= BITFIELD_MASK(MESA_SHADER_COMPUTE);
if (cmd->state.gfx.is_depth_stencil_dynamic)
mtl_release(cmd->state.gfx.depth_stencil_state);
cmd->state.gfx.depth_stencil_state = save->pipeline.gfx.ds;
cmd->state.gfx.is_depth_stencil_dynamic =
save->pipeline.gfx.is_ds_dynamic;
cmd->state.gfx.vb.addr_range[0] = save->vb0;
cmd->state.gfx.vb.handles[0] = save->vb0_handle;
cmd->state.gfx.dirty |= KK_DIRTY_VB;
cmd->state.gfx.occlusion.mode = save->pipeline.gfx.occlusion;
cmd->state.gfx.dirty |= KK_DIRTY_OCCLUSION;
desc->root_dirty = true;
} else {
kk_cmd_bind_compute_shader(cmd, save->shaders[MESA_SHADER_COMPUTE]);
}
cmd->state.cond_render = save->cond_render;
memcpy(desc->root.push, save->push, sizeof(save->push));
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdFillBuffer(VkCommandBuffer commandBuffer, VkBuffer dstBuffer,
VkDeviceSize dstOffset, VkDeviceSize dstRange, uint32_t data)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
struct kk_device *dev = kk_cmd_buffer_device(cmd);
struct kk_meta_save save;
kk_meta_begin(cmd, &save, VK_PIPELINE_BIND_POINT_COMPUTE);
vk_meta_fill_buffer(&cmd->vk, &dev->meta, dstBuffer, dstOffset, dstRange,
data);
kk_meta_end(cmd, &save, VK_PIPELINE_BIND_POINT_COMPUTE);
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdUpdateBuffer(VkCommandBuffer commandBuffer, VkBuffer dstBuffer,
VkDeviceSize dstOffset, VkDeviceSize dstRange,
const void *pData)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
struct kk_device *dev = kk_cmd_buffer_device(cmd);
struct kk_meta_save save;
kk_meta_begin(cmd, &save, VK_PIPELINE_BIND_POINT_COMPUTE);
vk_meta_update_buffer(&cmd->vk, &dev->meta, dstBuffer, dstOffset, dstRange,
pData);
kk_meta_end(cmd, &save, VK_PIPELINE_BIND_POINT_COMPUTE);
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdBlitImage2(VkCommandBuffer commandBuffer,
const VkBlitImageInfo2 *pBlitImageInfo)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
struct kk_device *dev = kk_cmd_buffer_device(cmd);
struct kk_meta_save save;
kk_meta_begin(cmd, &save, VK_PIPELINE_BIND_POINT_GRAPHICS);
vk_meta_blit_image2(&cmd->vk, &dev->meta, pBlitImageInfo);
kk_meta_end(cmd, &save, VK_PIPELINE_BIND_POINT_GRAPHICS);
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdResolveImage2(VkCommandBuffer commandBuffer,
const VkResolveImageInfo2 *pResolveImageInfo)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
struct kk_device *dev = kk_cmd_buffer_device(cmd);
struct kk_meta_save save;
kk_meta_begin(cmd, &save, VK_PIPELINE_BIND_POINT_GRAPHICS);
vk_meta_resolve_image2(&cmd->vk, &dev->meta, pResolveImageInfo);
kk_meta_end(cmd, &save, VK_PIPELINE_BIND_POINT_GRAPHICS);
}
static void
kk_meta_init_render(struct kk_cmd_buffer *cmd,
struct vk_meta_rendering_info *info)
{
const struct kk_rendering_state *render = &cmd->state.gfx.render;
*info = (struct vk_meta_rendering_info){
.samples = MAX2(render->samples, 1),
.view_mask = render->view_mask,
.color_attachment_count = render->color_att_count,
.depth_attachment_format = render->depth_att.vk_format,
.stencil_attachment_format = render->stencil_att.vk_format,
};
for (uint32_t a = 0; a < render->color_att_count; a++) {
info->color_attachment_formats[a] = render->color_att[a].vk_format;
info->color_attachment_write_masks[a] =
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
}
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdClearAttachments(VkCommandBuffer commandBuffer, uint32_t attachmentCount,
const VkClearAttachment *pAttachments,
uint32_t rectCount, const VkClearRect *pRects)
{
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
struct kk_device *dev = kk_cmd_buffer_device(cmd);
struct vk_meta_rendering_info render_info;
kk_meta_init_render(cmd, &render_info);
uint32_t view_mask = cmd->state.gfx.render.view_mask;
struct kk_encoder *encoder = cmd->encoder;
uint32_t layer_ids[KK_MAX_MULTIVIEW_VIEW_COUNT] = {};
mtl_set_vertex_amplification_count(encoder->main.encoder, layer_ids, 1u);
/* Preserve conditional rendering state for clearing attachments */
struct kk_conditional_rendering_state cond_render = cmd->state.cond_render;
struct kk_meta_save save;
kk_meta_begin(cmd, &save, VK_PIPELINE_BIND_POINT_GRAPHICS);
cmd->state.cond_render = cond_render;
vk_meta_clear_attachments(&cmd->vk, &dev->meta, &render_info,
attachmentCount, pAttachments, rectCount, pRects);
kk_meta_end(cmd, &save, VK_PIPELINE_BIND_POINT_GRAPHICS);
uint32_t count = 0u;
u_foreach_bit(id, view_mask)
layer_ids[count++] = id;
if (view_mask == 0u) {
layer_ids[count++] = 0;
}
mtl_set_vertex_amplification_count(encoder->main.encoder, layer_ids, count);
}
void
kk_meta_resolve_rendering(struct kk_cmd_buffer *cmd,
const VkRenderingInfo *pRenderingInfo)
{
struct kk_device *dev = kk_cmd_buffer_device(cmd);
struct kk_meta_save save;
kk_meta_begin(cmd, &save, VK_PIPELINE_BIND_POINT_GRAPHICS);
vk_meta_resolve_rendering(&cmd->vk, &dev->meta, pRenderingInfo);
kk_meta_end(cmd, &save, VK_PIPELINE_BIND_POINT_GRAPHICS);
}
+135
View File
@@ -0,0 +1,135 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_cmd_pool.h"
#include "kk_device.h"
#include "kk_entrypoints.h"
#include "kk_physical_device.h"
static VkResult
kk_cmd_bo_create(struct kk_cmd_pool *pool, struct kk_cmd_bo **bo_out)
{
struct kk_device *dev = kk_cmd_pool_device(pool);
struct kk_cmd_bo *bo = vk_zalloc(&pool->vk.alloc, sizeof(*bo), 8,
VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
if (bo == NULL)
return vk_error(pool, VK_ERROR_OUT_OF_HOST_MEMORY);
const VkResult result =
kk_alloc_bo(dev, &pool->vk.base, KK_CMD_BO_SIZE, 0, &bo->bo);
if (result != VK_SUCCESS) {
vk_free(&pool->vk.alloc, bo);
return vk_error(pool, VK_ERROR_OUT_OF_DEVICE_MEMORY);
}
*bo_out = bo;
return VK_SUCCESS;
}
static void
kk_cmd_bo_destroy(struct kk_cmd_pool *pool, struct kk_cmd_bo *bo)
{
struct kk_device *dev = kk_cmd_pool_device(pool);
kk_destroy_bo(dev, bo->bo);
vk_free(&pool->vk.alloc, bo);
}
static void
kk_cmd_pool_destroy_bos(struct kk_cmd_pool *pool)
{
list_for_each_entry_safe(struct kk_cmd_bo, bo, &pool->free_bos, link)
kk_cmd_bo_destroy(pool, bo);
list_inithead(&pool->free_bos);
}
VkResult
kk_cmd_pool_alloc_bo(struct kk_cmd_pool *pool, struct kk_cmd_bo **bo_out)
{
struct kk_cmd_bo *bo = NULL;
if (!list_is_empty(&pool->free_bos)) {
bo = list_first_entry(&pool->free_bos, struct kk_cmd_bo, link);
pool->num_free_bos--;
}
if (bo) {
list_del(&bo->link);
*bo_out = bo;
return VK_SUCCESS;
}
return kk_cmd_bo_create(pool, bo_out);
}
void
kk_cmd_pool_free_bo_list(struct kk_cmd_pool *pool, struct list_head *bos)
{
list_for_each_entry_safe(struct kk_cmd_bo, bo, bos, link) {
list_del(&bo->link);
if (pool->num_free_bos > KK_CMD_POOL_BO_MAX) {
kk_cmd_bo_destroy(pool, bo);
} else {
list_addtail(&bo->link, &pool->free_bos);
pool->num_free_bos++;
}
}
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_CreateCommandPool(VkDevice _device,
const VkCommandPoolCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator,
VkCommandPool *pCmdPool)
{
VK_FROM_HANDLE(kk_device, device, _device);
struct kk_cmd_pool *pool;
pool = vk_alloc2(&device->vk.alloc, pAllocator, sizeof(*pool), 8,
VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
if (pool == NULL)
return vk_error(device, VK_ERROR_OUT_OF_HOST_MEMORY);
VkResult result =
vk_command_pool_init(&device->vk, &pool->vk, pCreateInfo, pAllocator);
if (result != VK_SUCCESS) {
vk_free2(&device->vk.alloc, pAllocator, pool);
return result;
}
list_inithead(&pool->free_bos);
*pCmdPool = kk_cmd_pool_to_handle(pool);
return VK_SUCCESS;
}
VKAPI_ATTR void VKAPI_CALL
kk_DestroyCommandPool(VkDevice _device, VkCommandPool commandPool,
const VkAllocationCallbacks *pAllocator)
{
VK_FROM_HANDLE(kk_device, device, _device);
VK_FROM_HANDLE(kk_cmd_pool, pool, commandPool);
if (!pool)
return;
vk_command_pool_finish(&pool->vk);
kk_cmd_pool_destroy_bos(pool);
vk_free2(&device->vk.alloc, pAllocator, pool);
}
VKAPI_ATTR void VKAPI_CALL
kk_TrimCommandPool(VkDevice device, VkCommandPool commandPool,
VkCommandPoolTrimFlags flags)
{
VK_FROM_HANDLE(kk_cmd_pool, pool, commandPool);
vk_command_pool_trim(&pool->vk, flags);
kk_cmd_pool_destroy_bos(pool);
}
+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_CMD_POOL_H
#define KK_CMD_POOL_H
#include "kk_private.h"
#include "vk_command_pool.h"
#define KK_CMD_BO_SIZE (1024 * 128)
#define KK_CMD_POOL_BO_MAX 32
/* Recyclable command buffer BO, used for both push buffers and upload */
struct kk_cmd_bo {
struct kk_bo *bo;
/** Link in kk_cmd_pool::free_bos or kk_cmd_buffer::bos */
struct list_head link;
};
struct kk_cmd_pool {
struct vk_command_pool vk;
/** List of kk_cmd_bo */
struct list_head free_bos;
uint32_t num_free_bos;
};
VK_DEFINE_NONDISP_HANDLE_CASTS(kk_cmd_pool, vk.base, VkCommandPool,
VK_OBJECT_TYPE_COMMAND_POOL)
static inline struct kk_device *
kk_cmd_pool_device(struct kk_cmd_pool *pool)
{
return (struct kk_device *)pool->vk.base.device;
}
VkResult kk_cmd_pool_alloc_bo(struct kk_cmd_pool *pool,
struct kk_cmd_bo **bo_out);
void kk_cmd_pool_free_bo_list(struct kk_cmd_pool *pool, struct list_head *bos);
#endif /* KK_CMD_POOL_H */
+23
View File
@@ -0,0 +1,23 @@
/*
* Copyright 2025 LunarG, Inc.
* SPDX-License-Identifier: MIT
*/
#include "kk_debug.h"
#include "util/u_debug.h"
enum kk_debug kk_mesa_debug_flags = 0;
const struct debug_named_value flags[] = {
{"nir", KK_DEBUG_NIR},
{"msl", KK_DEBUG_MSL},
{"force_robustness", KK_DEBUG_FORCE_ROBUSTNESS},
{NULL, 0},
};
DEBUG_GET_ONCE_FLAGS_OPTION(mesa_kk_debug, "MESA_KK_DEBUG", flags, 0);
void
kk_process_debug_variable(void)
{
kk_mesa_debug_flags = debug_get_option_mesa_kk_debug();
}
+23
View File
@@ -0,0 +1,23 @@
/*
* Copyright 2025 LunarG, Inc.
* SPDX-License-Identifier: MIT
*/
#ifndef KK_DEBUG_H
#define KK_DEBUG_H 1
enum kk_debug {
/* Print out the NIR from the compiler */
KK_DEBUG_NIR = 1ull << 0,
/* Print out the generated MSL source code from the compiler */
KK_DEBUG_MSL = 1ull << 1,
/* Forces robustness for all shaders */
KK_DEBUG_FORCE_ROBUSTNESS = 1ull << 2,
};
extern enum kk_debug kk_mesa_debug_flags;
#define KK_DEBUG(flag) unlikely(kk_mesa_debug_flags &KK_DEBUG_##flag)
extern void kk_process_debug_variable(void);
#endif /* KK_DEBUG_H */
+805
View File
@@ -0,0 +1,805 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_descriptor_set.h"
#include "kk_bo.h"
#include "kk_buffer.h"
#include "kk_buffer_view.h"
#include "kk_descriptor_set_layout.h"
#include "kk_device.h"
#include "kk_entrypoints.h"
#include "kk_image_view.h"
#include "kk_physical_device.h"
#include "kk_sampler.h"
#include "util/format/u_format.h"
static inline uint32_t
align_u32(uint32_t v, uint32_t a)
{
assert(a != 0 && a == (a & -a));
return (v + a - 1) & ~(a - 1);
}
static inline void *
desc_ubo_data(struct kk_descriptor_set *set, uint32_t binding, uint32_t elem,
uint32_t *size_out)
{
const struct kk_descriptor_set_binding_layout *binding_layout =
&set->layout->binding[binding];
uint32_t offset = binding_layout->offset + elem * binding_layout->stride;
assert(offset < set->size);
if (size_out != NULL)
*size_out = set->size - offset;
return (char *)set->mapped_ptr + offset;
}
static void
write_desc(struct kk_descriptor_set *set, uint32_t binding, uint32_t elem,
const void *desc_data, size_t desc_size)
{
ASSERTED uint32_t dst_size;
void *dst = desc_ubo_data(set, binding, elem, &dst_size);
assert(desc_size <= dst_size);
memcpy(dst, desc_data, desc_size);
}
static void
get_sampled_image_view_desc(VkDescriptorType descriptor_type,
const VkDescriptorImageInfo *const info, void *dst,
size_t dst_size, bool is_input_attachment)
{
struct kk_sampled_image_descriptor desc[3] = {};
uint8_t plane_count = 1;
if (descriptor_type != VK_DESCRIPTOR_TYPE_SAMPLER && info &&
info->imageView != VK_NULL_HANDLE) {
VK_FROM_HANDLE(kk_image_view, view, info->imageView);
plane_count = view->plane_count;
for (uint8_t plane = 0; plane < plane_count; plane++) {
if (is_input_attachment) {
assert(view->planes[plane].sampled_gpu_resource_id);
desc[plane].image_gpu_resource_id =
view->planes[plane].input_gpu_resource_id;
} else {
assert(view->planes[plane].sampled_gpu_resource_id);
desc[plane].image_gpu_resource_id =
view->planes[plane].sampled_gpu_resource_id;
}
}
}
if (descriptor_type == VK_DESCRIPTOR_TYPE_SAMPLER ||
descriptor_type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) {
VK_FROM_HANDLE(kk_sampler, sampler, info->sampler);
plane_count = MAX2(plane_count, sampler->plane_count);
for (uint8_t plane = 0; plane < plane_count; plane++) {
/* We need to replicate the last sampler plane out to all image
* planes due to sampler table entry limitations. See
* nvk_CreateSampler in nvk_sampler.c for more details.
*/
uint8_t sampler_plane = MIN2(plane, sampler->plane_count - 1u);
assert(sampler->planes[sampler_plane].hw->handle);
desc[plane].sampler_index = sampler->planes[sampler_plane].hw->index;
desc[plane].lod_bias_fp16 = sampler->lod_bias_fp16;
desc[plane].lod_min_fp16 = sampler->lod_min_fp16;
desc[plane].lod_max_fp16 = sampler->lod_max_fp16;
}
}
assert(sizeof(desc[0]) * plane_count <= dst_size);
memcpy(dst, desc, sizeof(desc[0]) * plane_count);
}
static void
write_sampled_image_view_desc(struct kk_descriptor_set *set,
const VkDescriptorImageInfo *const _info,
uint32_t binding, uint32_t elem,
VkDescriptorType descriptor_type)
{
VkDescriptorImageInfo info = *_info;
struct kk_descriptor_set_binding_layout *binding_layout =
&set->layout->binding[binding];
if (descriptor_type == VK_DESCRIPTOR_TYPE_SAMPLER ||
descriptor_type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) {
if (binding_layout->immutable_samplers != NULL) {
info.sampler =
kk_sampler_to_handle(binding_layout->immutable_samplers[elem]);
}
}
uint32_t dst_size;
void *dst = desc_ubo_data(set, binding, elem, &dst_size);
get_sampled_image_view_desc(
descriptor_type, &info, dst, dst_size,
descriptor_type == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT);
}
static void
get_storage_image_view_desc(
struct kk_descriptor_set_binding_layout *binding_layout,
const VkDescriptorImageInfo *const info, void *dst, size_t dst_size)
{
struct kk_storage_image_descriptor desc = {};
if (info && info->imageView != VK_NULL_HANDLE) {
VK_FROM_HANDLE(kk_image_view, view, info->imageView);
/* Storage images are always single plane */
assert(view->plane_count == 1);
uint8_t plane = 0;
assert(view->planes[plane].storage_gpu_resource_id);
desc.image_gpu_resource_id = view->planes[plane].storage_gpu_resource_id;
}
assert(sizeof(desc) <= dst_size);
memcpy(dst, &desc, sizeof(desc));
}
static void
write_storage_image_view_desc(struct kk_descriptor_set *set,
const VkDescriptorImageInfo *const info,
uint32_t binding, uint32_t elem)
{
uint32_t dst_size;
void *dst = desc_ubo_data(set, binding, elem, &dst_size);
struct kk_descriptor_set_binding_layout *binding_layout =
&set->layout->binding[binding];
get_storage_image_view_desc(binding_layout, info, dst, dst_size);
}
static void
write_buffer_desc(struct kk_descriptor_set *set,
const VkDescriptorBufferInfo *const info, uint32_t binding,
uint32_t elem)
{
VK_FROM_HANDLE(kk_buffer, buffer, info->buffer);
const struct kk_addr_range addr_range =
kk_buffer_addr_range(buffer, info->offset, info->range);
assert(addr_range.range <= UINT32_MAX);
const struct kk_buffer_address desc = {
.base_addr = addr_range.addr,
.size = addr_range.range,
};
write_desc(set, binding, elem, &desc, sizeof(desc));
}
static void
write_dynamic_buffer_desc(struct kk_descriptor_set *set,
const VkDescriptorBufferInfo *const info,
uint32_t binding, uint32_t elem)
{
VK_FROM_HANDLE(kk_buffer, buffer, info->buffer);
const struct kk_descriptor_set_binding_layout *binding_layout =
&set->layout->binding[binding];
const struct kk_addr_range addr_range =
kk_buffer_addr_range(buffer, info->offset, info->range);
assert(addr_range.range <= UINT32_MAX);
struct kk_buffer_address *desc =
&set->dynamic_buffers[binding_layout->dynamic_buffer_index + elem];
*desc = (struct kk_buffer_address){
.base_addr = addr_range.addr,
.size = addr_range.range,
};
}
static void
write_buffer_view_desc(struct kk_descriptor_set *set,
const VkBufferView bufferView, uint32_t binding,
uint32_t elem)
{
struct kk_storage_image_descriptor desc = {};
if (bufferView != VK_NULL_HANDLE) {
VK_FROM_HANDLE(kk_buffer_view, view, bufferView);
assert(view->mtl_texel_buffer_handle);
assert(view->texel_buffer_gpu_id);
desc.image_gpu_resource_id = view->texel_buffer_gpu_id;
}
write_desc(set, binding, elem, &desc, sizeof(desc));
}
static void
write_inline_uniform_data(struct kk_descriptor_set *set,
const VkWriteDescriptorSetInlineUniformBlock *info,
uint32_t binding, uint32_t offset)
{
assert(set->layout->binding[binding].stride == 1);
write_desc(set, binding, offset, info->pData, info->dataSize);
}
VKAPI_ATTR void VKAPI_CALL
kk_UpdateDescriptorSets(VkDevice device, uint32_t descriptorWriteCount,
const VkWriteDescriptorSet *pDescriptorWrites,
uint32_t descriptorCopyCount,
const VkCopyDescriptorSet *pDescriptorCopies)
{
for (uint32_t w = 0; w < descriptorWriteCount; w++) {
const VkWriteDescriptorSet *write = &pDescriptorWrites[w];
VK_FROM_HANDLE(kk_descriptor_set, set, write->dstSet);
switch (write->descriptorType) {
case VK_DESCRIPTOR_TYPE_SAMPLER:
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
for (uint32_t j = 0; j < write->descriptorCount; j++) {
write_sampled_image_view_desc(
set, write->pImageInfo + j, write->dstBinding,
write->dstArrayElement + j, write->descriptorType);
}
break;
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
for (uint32_t j = 0; j < write->descriptorCount; j++) {
write_storage_image_view_desc(set, write->pImageInfo + j,
write->dstBinding,
write->dstArrayElement + j);
}
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
for (uint32_t j = 0; j < write->descriptorCount; j++) {
write_buffer_view_desc(set, write->pTexelBufferView[j],
write->dstBinding,
write->dstArrayElement + j);
}
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
for (uint32_t j = 0; j < write->descriptorCount; j++) {
write_buffer_desc(set, write->pBufferInfo + j, write->dstBinding,
write->dstArrayElement + j);
}
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
for (uint32_t j = 0; j < write->descriptorCount; j++) {
write_dynamic_buffer_desc(set, write->pBufferInfo + j,
write->dstBinding,
write->dstArrayElement + j);
}
break;
case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK: {
const VkWriteDescriptorSetInlineUniformBlock *write_inline =
vk_find_struct_const(write->pNext,
WRITE_DESCRIPTOR_SET_INLINE_UNIFORM_BLOCK);
assert(write_inline->dataSize == write->descriptorCount);
write_inline_uniform_data(set, write_inline, write->dstBinding,
write->dstArrayElement);
break;
}
default:
break;
}
}
for (uint32_t i = 0; i < descriptorCopyCount; i++) {
const VkCopyDescriptorSet *copy = &pDescriptorCopies[i];
VK_FROM_HANDLE(kk_descriptor_set, src, copy->srcSet);
VK_FROM_HANDLE(kk_descriptor_set, dst, copy->dstSet);
const struct kk_descriptor_set_binding_layout *src_binding_layout =
&src->layout->binding[copy->srcBinding];
const struct kk_descriptor_set_binding_layout *dst_binding_layout =
&dst->layout->binding[copy->dstBinding];
if (dst_binding_layout->stride > 0 && src_binding_layout->stride > 0) {
for (uint32_t j = 0; j < copy->descriptorCount; j++) {
ASSERTED uint32_t dst_max_size, src_max_size;
void *dst_map = desc_ubo_data(
dst, copy->dstBinding, copy->dstArrayElement + j, &dst_max_size);
const void *src_map = desc_ubo_data(
src, copy->srcBinding, copy->srcArrayElement + j, &src_max_size);
const uint32_t copy_size =
MIN2(dst_binding_layout->stride, src_binding_layout->stride);
assert(copy_size <= dst_max_size && copy_size <= src_max_size);
memcpy(dst_map, src_map, copy_size);
}
}
switch (src_binding_layout->type) {
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC: {
const uint32_t dst_dyn_start =
dst_binding_layout->dynamic_buffer_index + copy->dstArrayElement;
const uint32_t src_dyn_start =
src_binding_layout->dynamic_buffer_index + copy->srcArrayElement;
typed_memcpy(&dst->dynamic_buffers[dst_dyn_start],
&src->dynamic_buffers[src_dyn_start],
copy->descriptorCount);
break;
}
default:
break;
}
}
}
void
kk_push_descriptor_set_update(struct kk_push_descriptor_set *push_set,
uint32_t write_count,
const VkWriteDescriptorSet *writes)
{
struct kk_descriptor_set_layout *layout = push_set->layout;
assert(layout->non_variable_descriptor_buffer_size < sizeof(push_set->data));
struct kk_descriptor_set set = {
.layout = push_set->layout,
.size = sizeof(push_set->data),
.mapped_ptr = push_set->data,
};
for (uint32_t w = 0; w < write_count; w++) {
const VkWriteDescriptorSet *write = &writes[w];
assert(write->dstSet == VK_NULL_HANDLE);
switch (write->descriptorType) {
case VK_DESCRIPTOR_TYPE_SAMPLER:
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
for (uint32_t j = 0; j < write->descriptorCount; j++) {
write_sampled_image_view_desc(
&set, write->pImageInfo + j, write->dstBinding,
write->dstArrayElement + j, write->descriptorType);
}
break;
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
for (uint32_t j = 0; j < write->descriptorCount; j++) {
write_storage_image_view_desc(&set, write->pImageInfo + j,
write->dstBinding,
write->dstArrayElement + j);
}
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
for (uint32_t j = 0; j < write->descriptorCount; j++) {
write_buffer_view_desc(&set, write->pTexelBufferView[j],
write->dstBinding,
write->dstArrayElement + j);
}
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
for (uint32_t j = 0; j < write->descriptorCount; j++) {
write_buffer_desc(&set, write->pBufferInfo + j, write->dstBinding,
write->dstArrayElement + j);
}
break;
default:
break;
}
}
}
static void kk_descriptor_pool_free(struct kk_descriptor_pool *pool,
uint64_t addr, uint64_t size);
static void
kk_descriptor_set_destroy(struct kk_device *dev,
struct kk_descriptor_pool *pool,
struct kk_descriptor_set *set)
{
list_del(&set->link);
if (set->size > 0)
kk_descriptor_pool_free(pool, set->addr, set->size);
vk_descriptor_set_layout_unref(&dev->vk, &set->layout->vk);
vk_object_free(&dev->vk, NULL, set);
}
static void
kk_destroy_descriptor_pool(struct kk_device *dev,
const VkAllocationCallbacks *pAllocator,
struct kk_descriptor_pool *pool)
{
list_for_each_entry_safe(struct kk_descriptor_set, set, &pool->sets, link)
kk_descriptor_set_destroy(dev, pool, set);
util_vma_heap_finish(&pool->heap);
if (pool->bo != NULL)
kk_destroy_bo(dev, pool->bo);
vk_object_free(&dev->vk, pAllocator, pool);
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_CreateDescriptorPool(VkDevice _device,
const VkDescriptorPoolCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator,
VkDescriptorPool *pDescriptorPool)
{
VK_FROM_HANDLE(kk_device, dev, _device);
struct kk_descriptor_pool *pool;
VkResult result = VK_SUCCESS;
pool = vk_object_zalloc(&dev->vk, pAllocator, sizeof(*pool),
VK_OBJECT_TYPE_DESCRIPTOR_POOL);
if (!pool)
return vk_error(dev, VK_ERROR_OUT_OF_HOST_MEMORY);
list_inithead(&pool->sets);
const VkMutableDescriptorTypeCreateInfoEXT *mutable_info =
vk_find_struct_const(pCreateInfo->pNext,
MUTABLE_DESCRIPTOR_TYPE_CREATE_INFO_EXT);
uint32_t max_align = 0;
for (unsigned i = 0; i < pCreateInfo->poolSizeCount; ++i) {
const VkMutableDescriptorTypeListEXT *type_list = NULL;
if (pCreateInfo->pPoolSizes[i].type == VK_DESCRIPTOR_TYPE_MUTABLE_EXT &&
mutable_info && i < mutable_info->mutableDescriptorTypeListCount)
type_list = &mutable_info->pMutableDescriptorTypeLists[i];
uint32_t stride, alignment;
kk_descriptor_stride_align_for_type(pCreateInfo->pPoolSizes[i].type,
type_list, &stride, &alignment);
max_align = MAX2(max_align, alignment);
}
uint64_t mem_size = 0;
for (unsigned i = 0; i < pCreateInfo->poolSizeCount; ++i) {
const VkMutableDescriptorTypeListEXT *type_list = NULL;
if (pCreateInfo->pPoolSizes[i].type == VK_DESCRIPTOR_TYPE_MUTABLE_EXT &&
mutable_info && i < mutable_info->mutableDescriptorTypeListCount)
type_list = &mutable_info->pMutableDescriptorTypeLists[i];
uint32_t stride, alignment;
kk_descriptor_stride_align_for_type(pCreateInfo->pPoolSizes[i].type,
type_list, &stride, &alignment);
mem_size +=
MAX2(stride, max_align) * pCreateInfo->pPoolSizes[i].descriptorCount;
}
/* Individual descriptor sets are aligned to the min UBO alignment to
* ensure that we don't end up with unaligned data access in any shaders.
* This means that each descriptor buffer allocated may burn up to 16B of
* extra space to get the right alignment. (Technically, it's at most 28B
* because we're always going to start at least 4B aligned but we're being
* conservative here.) Allocate enough extra space that we can chop it
* into maxSets pieces and align each one of them to 32B.
*/
mem_size += kk_min_cbuf_alignment() * pCreateInfo->maxSets;
if (mem_size) {
result = kk_alloc_bo(dev, &dev->vk.base, mem_size, 0u, &pool->bo);
if (result != VK_SUCCESS) {
kk_destroy_descriptor_pool(dev, pAllocator, pool);
return result;
}
/* The BO may be larger thanks to GPU page alignment. We may as well
* make that extra space available to the client.
*/
assert(pool->bo->size_B >= mem_size);
util_vma_heap_init(&pool->heap, pool->bo->gpu, pool->bo->size_B);
} else {
util_vma_heap_init(&pool->heap, 0, 0);
}
*pDescriptorPool = kk_descriptor_pool_to_handle(pool);
return result;
}
static VkResult
kk_descriptor_pool_alloc(struct kk_descriptor_pool *pool, uint64_t size,
uint64_t alignment, uint64_t *addr_out, void **map_out)
{
assert(size > 0);
assert(size % alignment == 0);
if (size > pool->heap.free_size)
return VK_ERROR_OUT_OF_POOL_MEMORY;
uint64_t addr = util_vma_heap_alloc(&pool->heap, size, alignment);
if (addr == 0)
return VK_ERROR_FRAGMENTED_POOL;
assert(addr >= pool->bo->gpu);
assert(addr + size <= pool->bo->gpu + pool->bo->size_B);
uint64_t offset = addr - pool->bo->gpu;
*addr_out = addr;
*map_out = pool->bo->cpu + offset;
return VK_SUCCESS;
}
static void
kk_descriptor_pool_free(struct kk_descriptor_pool *pool, uint64_t addr,
uint64_t size)
{
assert(size > 0);
assert(addr >= pool->bo->gpu);
assert(addr + size <= pool->bo->gpu + pool->bo->size_B);
util_vma_heap_free(&pool->heap, addr, size);
}
static VkResult
kk_descriptor_set_create(struct kk_device *dev, struct kk_descriptor_pool *pool,
struct kk_descriptor_set_layout *layout,
uint32_t variable_count,
struct kk_descriptor_set **out_set)
{
struct kk_descriptor_set *set;
VkResult result = VK_SUCCESS;
uint32_t mem_size =
sizeof(struct kk_descriptor_set) +
layout->vk.dynamic_descriptor_count * sizeof(struct kk_buffer_address);
set =
vk_object_zalloc(&dev->vk, NULL, mem_size, VK_OBJECT_TYPE_DESCRIPTOR_SET);
if (!set)
return vk_error(dev, VK_ERROR_OUT_OF_HOST_MEMORY);
set->size = layout->non_variable_descriptor_buffer_size;
if (layout->binding_count > 0 &&
(layout->binding[layout->binding_count - 1].flags &
VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT)) {
uint32_t stride = layout->binding[layout->binding_count - 1].stride;
set->size += stride * variable_count;
}
uint32_t alignment = kk_min_cbuf_alignment();
set->size = align64(set->size, alignment);
if (set->size > 0) {
result = kk_descriptor_pool_alloc(pool, set->size, alignment, &set->addr,
&set->mapped_ptr);
if (result != VK_SUCCESS) {
vk_object_free(&dev->vk, NULL, set);
return result;
}
}
vk_descriptor_set_layout_ref(&layout->vk);
set->layout = layout;
for (uint32_t b = 0; b < layout->binding_count; b++) {
if (layout->binding[b].type != VK_DESCRIPTOR_TYPE_SAMPLER &&
layout->binding[b].type != VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER)
continue;
if (layout->binding[b].immutable_samplers == NULL)
continue;
uint32_t array_size = layout->binding[b].array_size;
if (layout->binding[b].flags &
VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT)
array_size = variable_count;
const VkDescriptorImageInfo empty = {};
for (uint32_t j = 0; j < array_size; j++) {
write_sampled_image_view_desc(set, &empty, b, j,
layout->binding[b].type);
}
}
list_addtail(&set->link, &pool->sets);
*out_set = set;
return result;
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_AllocateDescriptorSets(VkDevice device,
const VkDescriptorSetAllocateInfo *pAllocateInfo,
VkDescriptorSet *pDescriptorSets)
{
VK_FROM_HANDLE(kk_device, dev, device);
VK_FROM_HANDLE(kk_descriptor_pool, pool, pAllocateInfo->descriptorPool);
VkResult result = VK_SUCCESS;
uint32_t i;
struct kk_descriptor_set *set = NULL;
const VkDescriptorSetVariableDescriptorCountAllocateInfo *var_desc_count =
vk_find_struct_const(
pAllocateInfo->pNext,
DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO);
/* allocate a set of buffers for each shader to contain descriptors */
for (i = 0; i < pAllocateInfo->descriptorSetCount; i++) {
VK_FROM_HANDLE(kk_descriptor_set_layout, layout,
pAllocateInfo->pSetLayouts[i]);
/* If descriptorSetCount is zero or this structure is not included in
* the pNext chain, then the variable lengths are considered to be zero.
*/
const uint32_t variable_count =
var_desc_count && var_desc_count->descriptorSetCount > 0
? var_desc_count->pDescriptorCounts[i]
: 0;
result =
kk_descriptor_set_create(dev, pool, layout, variable_count, &set);
if (result != VK_SUCCESS)
break;
pDescriptorSets[i] = kk_descriptor_set_to_handle(set);
}
if (result != VK_SUCCESS) {
kk_FreeDescriptorSets(device, pAllocateInfo->descriptorPool, i,
pDescriptorSets);
for (i = 0; i < pAllocateInfo->descriptorSetCount; i++) {
pDescriptorSets[i] = VK_NULL_HANDLE;
}
}
return result;
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_FreeDescriptorSets(VkDevice device, VkDescriptorPool descriptorPool,
uint32_t descriptorSetCount,
const VkDescriptorSet *pDescriptorSets)
{
VK_FROM_HANDLE(kk_device, dev, device);
VK_FROM_HANDLE(kk_descriptor_pool, pool, descriptorPool);
for (uint32_t i = 0; i < descriptorSetCount; i++) {
VK_FROM_HANDLE(kk_descriptor_set, set, pDescriptorSets[i]);
if (set)
kk_descriptor_set_destroy(dev, pool, set);
}
return VK_SUCCESS;
}
VKAPI_ATTR void VKAPI_CALL
kk_DestroyDescriptorPool(VkDevice device, VkDescriptorPool _pool,
const VkAllocationCallbacks *pAllocator)
{
VK_FROM_HANDLE(kk_device, dev, device);
VK_FROM_HANDLE(kk_descriptor_pool, pool, _pool);
if (!_pool)
return;
kk_destroy_descriptor_pool(dev, pAllocator, pool);
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_ResetDescriptorPool(VkDevice device, VkDescriptorPool descriptorPool,
VkDescriptorPoolResetFlags flags)
{
VK_FROM_HANDLE(kk_device, dev, device);
VK_FROM_HANDLE(kk_descriptor_pool, pool, descriptorPool);
list_for_each_entry_safe(struct kk_descriptor_set, set, &pool->sets, link)
kk_descriptor_set_destroy(dev, pool, set);
return VK_SUCCESS;
}
static void
kk_descriptor_set_write_template(
struct kk_descriptor_set *set,
const struct vk_descriptor_update_template *template, const void *data)
{
for (uint32_t i = 0; i < template->entry_count; i++) {
const struct vk_descriptor_template_entry *entry = &template->entries[i];
switch (entry->type) {
case VK_DESCRIPTOR_TYPE_SAMPLER:
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
for (uint32_t j = 0; j < entry->array_count; j++) {
const VkDescriptorImageInfo *info =
data + entry->offset + j * entry->stride;
write_sampled_image_view_desc(set, info, entry->binding,
entry->array_element + j,
entry->type);
}
break;
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
for (uint32_t j = 0; j < entry->array_count; j++) {
const VkDescriptorImageInfo *info =
data + entry->offset + j * entry->stride;
write_storage_image_view_desc(set, info, entry->binding,
entry->array_element + j);
}
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
for (uint32_t j = 0; j < entry->array_count; j++) {
const VkBufferView *bview =
data + entry->offset + j * entry->stride;
write_buffer_view_desc(set, *bview, entry->binding,
entry->array_element + j);
}
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
for (uint32_t j = 0; j < entry->array_count; j++) {
const VkDescriptorBufferInfo *info =
data + entry->offset + j * entry->stride;
write_buffer_desc(set, info, entry->binding,
entry->array_element + j);
}
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
for (uint32_t j = 0; j < entry->array_count; j++) {
const VkDescriptorBufferInfo *info =
data + entry->offset + j * entry->stride;
write_dynamic_buffer_desc(set, info, entry->binding,
entry->array_element + j);
}
break;
case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK:
write_desc(set, entry->binding, entry->array_element,
data + entry->offset, entry->array_count);
break;
default:
break;
}
}
}
VKAPI_ATTR void VKAPI_CALL
kk_UpdateDescriptorSetWithTemplate(
VkDevice device, VkDescriptorSet descriptorSet,
VkDescriptorUpdateTemplate descriptorUpdateTemplate, const void *pData)
{
VK_FROM_HANDLE(kk_descriptor_set, set, descriptorSet);
VK_FROM_HANDLE(vk_descriptor_update_template, template,
descriptorUpdateTemplate);
kk_descriptor_set_write_template(set, template, pData);
}
void
kk_push_descriptor_set_update_template(
struct kk_push_descriptor_set *push_set,
struct kk_descriptor_set_layout *layout,
const struct vk_descriptor_update_template *template, const void *data)
{
struct kk_descriptor_set tmp_set = {
.layout = layout,
.size = sizeof(push_set->data),
.mapped_ptr = push_set->data,
};
kk_descriptor_set_write_template(&tmp_set, template, data);
}
@@ -0,0 +1,77 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_DESCRIPTOR_SET
#define KK_DESCRIPTOR_SET 1
#include "kk_private.h"
#include "kk_descriptor_types.h"
#include "kk_device.h"
#include "vk_descriptor_update_template.h"
#include "vk_object.h"
#include "util/list.h"
#include "util/vma.h"
struct kk_descriptor_set_layout;
struct kk_bo;
struct kk_descriptor_pool {
struct vk_object_base base;
struct list_head sets;
struct kk_bo *bo;
struct util_vma_heap heap;
};
VK_DEFINE_NONDISP_HANDLE_CASTS(kk_descriptor_pool, base, VkDescriptorPool,
VK_OBJECT_TYPE_DESCRIPTOR_POOL)
struct kk_descriptor_set {
struct vk_object_base base;
/* Link in kk_descriptor_pool::sets */
struct list_head link;
struct kk_descriptor_set_layout *layout;
void *mapped_ptr;
uint64_t addr;
uint32_t size;
struct kk_buffer_address dynamic_buffers[];
};
VK_DEFINE_NONDISP_HANDLE_CASTS(kk_descriptor_set, base, VkDescriptorSet,
VK_OBJECT_TYPE_DESCRIPTOR_SET)
static inline struct kk_buffer_address
kk_descriptor_set_addr(const struct kk_descriptor_set *set)
{
return (struct kk_buffer_address){
.base_addr = set->addr,
.size = set->size,
};
}
struct kk_push_descriptor_set {
uint8_t data[KK_PUSH_DESCRIPTOR_SET_SIZE];
struct kk_descriptor_set_layout *layout;
};
void kk_push_descriptor_set_update(struct kk_push_descriptor_set *push_set,
uint32_t write_count,
const VkWriteDescriptorSet *writes);
void kk_push_descriptor_set_update_template(
struct kk_push_descriptor_set *push_set,
struct kk_descriptor_set_layout *layout,
const struct vk_descriptor_update_template *template, const void *data);
#endif
@@ -0,0 +1,507 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_descriptor_set_layout.h"
#include "kk_descriptor_types.h"
#include "kk_device.h"
#include "kk_entrypoints.h"
#include "kk_physical_device.h"
#include "kk_sampler.h"
#include "vk_pipeline_layout.h"
static bool
binding_has_immutable_samplers(const VkDescriptorSetLayoutBinding *binding)
{
switch (binding->descriptorType) {
case VK_DESCRIPTOR_TYPE_SAMPLER:
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
return binding->pImmutableSamplers != NULL;
default:
return false;
}
}
void
kk_descriptor_stride_align_for_type(
VkDescriptorType type, const VkMutableDescriptorTypeListEXT *type_list,
uint32_t *stride, uint32_t *alignment)
{
switch (type) {
case VK_DESCRIPTOR_TYPE_SAMPLER:
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
/* TODO: How do samplers work? */
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
*stride = *alignment = sizeof(struct kk_sampled_image_descriptor);
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
*stride = *alignment = sizeof(struct kk_storage_image_descriptor);
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
*stride = *alignment = sizeof(struct kk_buffer_address);
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
*stride = *alignment = 0; /* These don't take up buffer space */
break;
case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK:
*stride = 1; /* Array size is bytes */
*alignment = kk_min_cbuf_alignment();
break;
case VK_DESCRIPTOR_TYPE_MUTABLE_EXT:
*stride = *alignment = 0;
if (type_list == NULL || type_list->descriptorTypeCount == 0u)
*stride = *alignment = KK_MAX_DESCRIPTOR_SIZE;
for (unsigned i = 0; type_list && i < type_list->descriptorTypeCount;
i++) {
/* This shouldn't recurse */
assert(type_list->pDescriptorTypes[i] !=
VK_DESCRIPTOR_TYPE_MUTABLE_EXT);
uint32_t desc_stride, desc_align;
kk_descriptor_stride_align_for_type(type_list->pDescriptorTypes[i],
NULL, &desc_stride, &desc_align);
*stride = MAX2(*stride, desc_stride);
*alignment = MAX2(*alignment, desc_align);
}
*stride = align(*stride, *alignment);
break;
default:
UNREACHABLE("Invalid descriptor type");
}
assert(*stride <= KK_MAX_DESCRIPTOR_SIZE);
}
static const VkMutableDescriptorTypeListEXT *
kk_descriptor_get_type_list(VkDescriptorType type,
const VkMutableDescriptorTypeCreateInfoEXT *info,
const uint32_t info_idx)
{
const VkMutableDescriptorTypeListEXT *type_list = NULL;
if (type == VK_DESCRIPTOR_TYPE_MUTABLE_EXT) {
assert(info != NULL);
assert(info_idx < info->mutableDescriptorTypeListCount);
type_list = &info->pMutableDescriptorTypeLists[info_idx];
}
return type_list;
}
static void
kk_descriptor_set_layout_destroy(struct vk_device *vk_dev,
struct vk_descriptor_set_layout *vk_layout)
{
struct kk_device *dev = container_of(vk_dev, struct kk_device, vk);
struct kk_descriptor_set_layout *layout =
vk_to_kk_descriptor_set_layout(vk_layout);
vk_object_free(&dev->vk, NULL, layout);
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_CreateDescriptorSetLayout(VkDevice device,
const VkDescriptorSetLayoutCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator,
VkDescriptorSetLayout *pSetLayout)
{
VK_FROM_HANDLE(kk_device, dev, device);
uint32_t num_bindings = 0;
uint32_t immutable_sampler_count = 0;
for (uint32_t j = 0; j < pCreateInfo->bindingCount; j++) {
const VkDescriptorSetLayoutBinding *binding = &pCreateInfo->pBindings[j];
num_bindings = MAX2(num_bindings, binding->binding + 1);
/* From the Vulkan 1.1.97 spec for VkDescriptorSetLayoutBinding:
*
* "If descriptorType specifies a VK_DESCRIPTOR_TYPE_SAMPLER or
* VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER type descriptor, then
* pImmutableSamplers can be used to initialize a set of immutable
* samplers. [...] If descriptorType is not one of these descriptor
* types, then pImmutableSamplers is ignored.
*
* We need to be careful here and only parse pImmutableSamplers if we
* have one of the right descriptor types.
*/
if (binding_has_immutable_samplers(binding))
immutable_sampler_count += binding->descriptorCount;
}
VK_MULTIALLOC(ma);
VK_MULTIALLOC_DECL(&ma, struct kk_descriptor_set_layout, layout, 1);
VK_MULTIALLOC_DECL(&ma, struct kk_descriptor_set_binding_layout, bindings,
num_bindings);
VK_MULTIALLOC_DECL(&ma, struct kk_sampler *, samplers,
immutable_sampler_count);
if (!vk_descriptor_set_layout_multizalloc(&dev->vk, &ma, pCreateInfo))
return vk_error(dev, VK_ERROR_OUT_OF_HOST_MEMORY);
layout->vk.destroy = kk_descriptor_set_layout_destroy;
layout->flags = pCreateInfo->flags;
layout->binding_count = num_bindings;
for (uint32_t j = 0; j < pCreateInfo->bindingCount; j++) {
const VkDescriptorSetLayoutBinding *binding = &pCreateInfo->pBindings[j];
uint32_t b = binding->binding;
/* We temporarily store pCreateInfo->pBindings[] index (plus one) in the
* immutable_samplers pointer. This provides us with a quick-and-dirty
* way to sort the bindings by binding number.
*/
layout->binding[b].immutable_samplers = (void *)(uintptr_t)(j + 1);
}
const VkDescriptorSetLayoutBindingFlagsCreateInfo *binding_flags_info =
vk_find_struct_const(pCreateInfo->pNext,
DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO);
const VkMutableDescriptorTypeCreateInfoEXT *mutable_info =
vk_find_struct_const(pCreateInfo->pNext,
MUTABLE_DESCRIPTOR_TYPE_CREATE_INFO_EXT);
uint32_t buffer_size = 0;
uint32_t max_variable_descriptor_size = 0;
uint8_t dynamic_buffer_count = 0;
for (uint32_t b = 0; b < num_bindings; b++) {
/* We stashed the pCreateInfo->pBindings[] index (plus one) in the
* immutable_samplers pointer. Check for NULL (empty binding) and then
* reset it and compute the index.
*/
if (layout->binding[b].immutable_samplers == NULL)
continue;
const uint32_t info_idx =
(uintptr_t)(void *)layout->binding[b].immutable_samplers - 1;
layout->binding[b].immutable_samplers = NULL;
const VkDescriptorSetLayoutBinding *binding =
&pCreateInfo->pBindings[info_idx];
if (binding->descriptorCount == 0)
continue;
layout->binding[b].type = binding->descriptorType;
layout->descriptor_count += binding->descriptorCount;
if (binding_flags_info && binding_flags_info->bindingCount > 0) {
assert(binding_flags_info->bindingCount == pCreateInfo->bindingCount);
layout->binding[b].flags = binding_flags_info->pBindingFlags[info_idx];
}
layout->binding[b].array_size = binding->descriptorCount;
switch (binding->descriptorType) {
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
layout->binding[b].dynamic_buffer_index = dynamic_buffer_count;
BITSET_SET_COUNT(layout->dynamic_ubos, dynamic_buffer_count,
binding->descriptorCount);
dynamic_buffer_count += binding->descriptorCount;
break;
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
layout->binding[b].dynamic_buffer_index = dynamic_buffer_count;
dynamic_buffer_count += binding->descriptorCount;
break;
default:
break;
}
const VkMutableDescriptorTypeListEXT *type_list =
kk_descriptor_get_type_list(binding->descriptorType, mutable_info,
info_idx);
uint32_t stride, alignment;
kk_descriptor_stride_align_for_type(binding->descriptorType, type_list,
&stride, &alignment);
uint8_t max_plane_count = 1;
if (binding_has_immutable_samplers(binding)) {
layout->binding[b].immutable_samplers = samplers;
samplers += binding->descriptorCount;
for (uint32_t i = 0; i < binding->descriptorCount; i++) {
VK_FROM_HANDLE(kk_sampler, sampler, binding->pImmutableSamplers[i]);
layout->binding[b].immutable_samplers[i] = sampler;
const uint8_t sampler_plane_count =
sampler->vk.ycbcr_conversion
? vk_format_get_plane_count(
sampler->vk.ycbcr_conversion->state.format)
: 1;
if (max_plane_count < sampler_plane_count)
max_plane_count = sampler_plane_count;
}
}
stride *= max_plane_count;
if (stride > 0) {
assert(stride <= UINT8_MAX);
assert(util_is_power_of_two_nonzero(alignment));
buffer_size = align64(buffer_size, alignment);
layout->binding[b].offset = buffer_size;
layout->binding[b].stride = stride;
if (layout->binding[b].flags &
VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT) {
/* From the Vulkan 1.3.256 spec:
*
* VUID-VkDescriptorSetLayoutBindingFlagsCreateInfo-pBindingFlags-03004
* "If an element of pBindingFlags includes
* VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT, then
* all other elements of
* VkDescriptorSetLayoutCreateInfo::pBindings must have a
* smaller value of binding"
*
* In other words, it has to be the last binding.
*/
assert(b == num_bindings - 1);
assert(max_variable_descriptor_size == 0);
max_variable_descriptor_size = stride * binding->descriptorCount;
} else {
/* the allocation size will be computed at descriptor allocation,
* but the buffer size will be already aligned as this binding will
* be the last
*/
buffer_size += stride * binding->descriptorCount;
}
}
}
layout->non_variable_descriptor_buffer_size = buffer_size;
layout->max_buffer_size = buffer_size + max_variable_descriptor_size;
layout->vk.dynamic_descriptor_count = dynamic_buffer_count;
struct mesa_blake3 blake3_ctx;
_mesa_blake3_init(&blake3_ctx);
#define BLAKE3_UPDATE_VALUE(x) \
_mesa_blake3_update(&blake3_ctx, &(x), sizeof(x));
BLAKE3_UPDATE_VALUE(layout->non_variable_descriptor_buffer_size);
BLAKE3_UPDATE_VALUE(layout->vk.dynamic_descriptor_count);
BLAKE3_UPDATE_VALUE(layout->binding_count);
for (uint32_t b = 0; b < num_bindings; b++) {
BLAKE3_UPDATE_VALUE(layout->binding[b].type);
BLAKE3_UPDATE_VALUE(layout->binding[b].flags);
BLAKE3_UPDATE_VALUE(layout->binding[b].array_size);
BLAKE3_UPDATE_VALUE(layout->binding[b].offset);
BLAKE3_UPDATE_VALUE(layout->binding[b].stride);
BLAKE3_UPDATE_VALUE(layout->binding[b].dynamic_buffer_index);
if (layout->binding[b].immutable_samplers != NULL) {
for (uint32_t i = 0; i < layout->binding[b].array_size; i++) {
const struct kk_sampler *sampler =
layout->binding[b].immutable_samplers[i];
/* We zalloc the object, so it's safe to hash the whole thing */
if (sampler != NULL && sampler->vk.ycbcr_conversion != NULL)
BLAKE3_UPDATE_VALUE(sampler->vk.ycbcr_conversion->state);
}
}
}
#undef BLAKE3_UPDATE_VALUE
_mesa_blake3_final(&blake3_ctx, layout->vk.blake3);
if (pCreateInfo->flags &
VK_DESCRIPTOR_SET_LAYOUT_CREATE_EMBEDDED_IMMUTABLE_SAMPLERS_BIT_EXT) {
void *sampler_desc_data =
vk_alloc2(&dev->vk.alloc, pAllocator, buffer_size, 4,
VK_SYSTEM_ALLOCATION_SCOPE_COMMAND);
if (sampler_desc_data == NULL) {
kk_descriptor_set_layout_destroy(&dev->vk, &layout->vk);
return vk_error(dev, VK_ERROR_OUT_OF_HOST_MEMORY);
}
for (uint32_t b = 0; b < num_bindings; b++) {
assert(layout->binding[b].type == VK_DESCRIPTOR_TYPE_SAMPLER);
assert(layout->binding[b].array_size == 1);
assert(layout->binding[b].immutable_samplers != NULL);
assert(!(layout->binding[b].flags &
VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT));
/* I'm paranoid */
if (layout->binding[b].immutable_samplers == NULL)
continue;
struct kk_sampler *sampler = layout->binding[b].immutable_samplers[0];
/* YCbCr has to come in through a combined image/sampler */
assert(sampler->plane_count == 1);
assert(sampler->planes[0].hw->handle);
}
vk_free2(&dev->vk.alloc, pAllocator, sampler_desc_data);
}
*pSetLayout = kk_descriptor_set_layout_to_handle(layout);
return VK_SUCCESS;
}
VKAPI_ATTR void VKAPI_CALL
kk_GetDescriptorSetLayoutSupport(
VkDevice device, const VkDescriptorSetLayoutCreateInfo *pCreateInfo,
VkDescriptorSetLayoutSupport *pSupport)
{
const VkMutableDescriptorTypeCreateInfoEXT *mutable_info =
vk_find_struct_const(pCreateInfo->pNext,
MUTABLE_DESCRIPTOR_TYPE_CREATE_INFO_EXT);
const VkDescriptorSetLayoutBindingFlagsCreateInfo *binding_flags =
vk_find_struct_const(pCreateInfo->pNext,
DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO);
/* Figure out the maximum alignment up-front. Otherwise, we need to sort
* the list of descriptors by binding number in order to get the size
* accumulation right.
*/
uint32_t max_align = 0;
for (uint32_t i = 0; i < pCreateInfo->bindingCount; i++) {
const VkDescriptorSetLayoutBinding *binding = &pCreateInfo->pBindings[i];
const VkMutableDescriptorTypeListEXT *type_list =
kk_descriptor_get_type_list(binding->descriptorType, mutable_info, i);
uint32_t stride, alignment;
kk_descriptor_stride_align_for_type(binding->descriptorType, type_list,
&stride, &alignment);
max_align = MAX2(max_align, alignment);
}
uint64_t non_variable_size = 0;
uint32_t variable_stride = 0;
uint32_t variable_count = 0;
uint8_t dynamic_buffer_count = 0;
bool variable_is_inline_uniform_block = false;
for (uint32_t i = 0; i < pCreateInfo->bindingCount; i++) {
const VkDescriptorSetLayoutBinding *binding = &pCreateInfo->pBindings[i];
VkDescriptorBindingFlags flags = 0;
if (binding_flags != NULL && binding_flags->bindingCount > 0)
flags = binding_flags->pBindingFlags[i];
switch (binding->descriptorType) {
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
dynamic_buffer_count += binding->descriptorCount;
break;
default:
break;
}
const VkMutableDescriptorTypeListEXT *type_list =
kk_descriptor_get_type_list(binding->descriptorType, mutable_info, i);
uint32_t stride, alignment;
kk_descriptor_stride_align_for_type(binding->descriptorType, type_list,
&stride, &alignment);
if (stride > 0) {
assert(stride <= UINT8_MAX);
assert(util_is_power_of_two_nonzero(alignment));
if (flags & VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT) {
/* From the Vulkan 1.3.256 spec:
*
* "For the purposes of this command, a variable-sized
* descriptor binding with a descriptorCount of zero is treated
* as if the descriptorCount is one"
*/
variable_count = MAX2(1, binding->descriptorCount);
variable_stride = stride;
variable_is_inline_uniform_block =
binding->descriptorType ==
VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK;
} else {
/* Since we're aligning to the maximum and since this is just a
* check for whether or not the max buffer size is big enough, we
* keep non_variable_size aligned to max_align.
*/
non_variable_size += stride * binding->descriptorCount;
non_variable_size = align64(non_variable_size, max_align);
}
}
}
uint64_t buffer_size = non_variable_size;
if (variable_stride > 0) {
buffer_size += variable_stride * variable_count;
buffer_size = align64(buffer_size, max_align);
}
uint32_t max_buffer_size;
if (pCreateInfo->flags &
VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR)
max_buffer_size = KK_PUSH_DESCRIPTOR_SET_SIZE;
else
max_buffer_size = KK_MAX_DESCRIPTOR_SET_SIZE;
pSupport->supported = dynamic_buffer_count <= KK_MAX_DYNAMIC_BUFFERS &&
buffer_size <= max_buffer_size;
vk_foreach_struct(ext, pSupport->pNext) {
switch (ext->sType) {
case VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_LAYOUT_SUPPORT: {
VkDescriptorSetVariableDescriptorCountLayoutSupport *vs = (void *)ext;
uint32_t max_var_count;
if (variable_stride > 0) {
max_var_count =
(max_buffer_size - non_variable_size) / variable_stride;
} else {
max_var_count = 0;
}
if (variable_is_inline_uniform_block) {
max_var_count =
MIN2(max_var_count, KK_MAX_INLINE_UNIFORM_BLOCK_SIZE);
}
vs->maxVariableDescriptorCount = max_var_count;
break;
}
default:
vk_debug_ignored_stype(ext->sType);
break;
}
}
}
VKAPI_ATTR void VKAPI_CALL
kk_GetDescriptorSetLayoutSizeEXT(VkDevice device, VkDescriptorSetLayout _layout,
VkDeviceSize *pLayoutSizeInBytes)
{
VK_FROM_HANDLE(kk_descriptor_set_layout, layout, _layout);
*pLayoutSizeInBytes = layout->max_buffer_size;
}
VKAPI_ATTR void VKAPI_CALL
kk_GetDescriptorSetLayoutBindingOffsetEXT(VkDevice device,
VkDescriptorSetLayout _layout,
uint32_t binding,
VkDeviceSize *pOffset)
{
VK_FROM_HANDLE(kk_descriptor_set_layout, layout, _layout);
*pOffset = layout->binding[binding].offset;
}
@@ -0,0 +1,94 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_DESCRIPTOR_SET_LAYOUT
#define KK_DESCRIPTOR_SET_LAYOUT 1
#include "kk_private.h"
#include "vk_descriptor_set_layout.h"
#include "vk_object.h"
#include "util/bitset.h"
struct kk_device;
struct kk_physical_device;
struct kk_sampler;
struct vk_pipeline_layout;
struct kk_descriptor_set_binding_layout {
/* The type of the descriptors in this binding */
VkDescriptorType type;
/* Flags provided when this binding was created */
VkDescriptorBindingFlags flags;
/* Number of array elements in this binding (or size in bytes for inline
* uniform data)
*/
uint32_t array_size;
/* Offset into the descriptor buffer where this descriptor lives */
uint32_t offset;
/* Stride between array elements in the descriptor buffer */
uint8_t stride;
/* Index into the dynamic buffer binding array */
uint8_t dynamic_buffer_index;
/* Immutable samplers (or NULL if no immutable samplers) */
struct kk_sampler **immutable_samplers;
};
struct kk_descriptor_set_layout {
struct vk_descriptor_set_layout vk;
VkDescriptorSetLayoutCreateFlagBits flags;
/* Size of the descriptor buffer for this descriptor set */
/* Does not contain the size needed for variable count descriptors */
uint32_t non_variable_descriptor_buffer_size;
/* Maximum possible buffer size for this descriptor set */
uint32_t max_buffer_size;
/* Which dynamic buffers are UBOs */
BITSET_DECLARE(dynamic_ubos, KK_MAX_DYNAMIC_BUFFERS);
/* Number of bindings in this descriptor set */
uint32_t binding_count;
/* Number of descriptors in the layout */
uint32_t descriptor_count;
/* Address to the embedded sampler descriptor buffer.
*
* This is allocated from nvk_device::heap and has the size
* non_variable_descriptor_buffer_size.
*/
uint64_t embedded_samplers_addr;
/* Bindings in this descriptor set */
struct kk_descriptor_set_binding_layout binding[0];
};
VK_DEFINE_NONDISP_HANDLE_CASTS(kk_descriptor_set_layout, vk.base,
VkDescriptorSetLayout,
VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT)
void kk_descriptor_stride_align_for_type(
VkDescriptorType type, const VkMutableDescriptorTypeListEXT *type_list,
uint32_t *stride, uint32_t *alignment);
static inline struct kk_descriptor_set_layout *
vk_to_kk_descriptor_set_layout(struct vk_descriptor_set_layout *layout)
{
return container_of(layout, struct kk_descriptor_set_layout, vk);
}
#endif /* KK_DESCRIPTOR_SET_LAYOUT */
@@ -0,0 +1,45 @@
/*
* Copyright © 2024 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_DESCRIPTOR_TYPES
#define KK_DESCRIPTOR_TYPES 1
#include "kk_private.h"
/* TODO_KOSMICKRISP Reduce size to 32 bytes by moving border to a heap. */
struct kk_sampled_image_descriptor {
uint64_t image_gpu_resource_id;
uint16_t sampler_index;
uint16_t lod_bias_fp16;
uint16_t lod_min_fp16;
uint16_t lod_max_fp16;
uint32_t has_border;
uint32_t pad_to_64_bits;
uint32_t border[4];
uint64_t pad_to_power_2[3];
};
static_assert(sizeof(struct kk_sampled_image_descriptor) == 64,
"kk_sampled_image_descriptor has no holes");
struct kk_storage_image_descriptor {
uint64_t image_gpu_resource_id;
};
static_assert(sizeof(struct kk_storage_image_descriptor) == 8,
"kk_storage_image_descriptor has no holes");
/* This has to match nir_address_format_64bit_bounded_global */
struct kk_buffer_address {
uint64_t base_addr;
uint32_t size;
uint32_t zero; /* Must be zero! */
};
static_assert(sizeof(struct kk_buffer_address) == 16,
"kk_buffer_address has no holes");
#endif /* KK_DESCRIPTOR_TYPES */
+375
View File
@@ -0,0 +1,375 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_device.h"
#include "kk_cmd_buffer.h"
#include "kk_entrypoints.h"
#include "kk_instance.h"
#include "kk_physical_device.h"
#include "kk_shader.h"
#include "kosmickrisp/bridge/mtl_bridge.h"
#include "vk_cmd_enqueue_entrypoints.h"
#include "vk_common_entrypoints.h"
#include "vulkan/wsi/wsi_common.h"
#include "vk_pipeline_cache.h"
#include <time.h>
DERIVE_HASH_TABLE(mtl_sampler_packed);
static VkResult
kk_init_sampler_heap(struct kk_device *dev, struct kk_sampler_heap *h)
{
h->ht = mtl_sampler_packed_table_create(NULL);
if (!h->ht)
return VK_ERROR_OUT_OF_HOST_MEMORY;
VkResult result = kk_query_table_init(dev, &h->table, 1024);
if (result != VK_SUCCESS) {
ralloc_free(h->ht);
return result;
}
simple_mtx_init(&h->lock, mtx_plain);
return VK_SUCCESS;
}
static void
kk_destroy_sampler_heap(struct kk_device *dev, struct kk_sampler_heap *h)
{
struct hash_entry *entry = _mesa_hash_table_next_entry(h->ht, NULL);
while (entry) {
struct kk_rc_sampler *sampler = (struct kk_rc_sampler *)entry->data;
mtl_release(sampler->handle);
entry = _mesa_hash_table_next_entry(h->ht, entry);
}
kk_query_table_finish(dev, &h->table);
ralloc_free(h->ht);
simple_mtx_destroy(&h->lock);
}
static VkResult
kk_sampler_heap_add_locked(struct kk_device *dev, struct kk_sampler_heap *h,
struct mtl_sampler_packed desc,
struct kk_rc_sampler **out)
{
struct hash_entry *ent = _mesa_hash_table_search(h->ht, &desc);
if (ent != NULL) {
*out = ent->data;
assert((*out)->refcount != 0);
(*out)->refcount++;
return VK_SUCCESS;
}
struct kk_rc_sampler *rc = ralloc(h->ht, struct kk_rc_sampler);
if (!rc)
return VK_ERROR_OUT_OF_HOST_MEMORY;
mtl_sampler *handle = kk_sampler_create(dev, &desc);
uint64_t gpu_id = mtl_sampler_get_gpu_resource_id(handle);
uint32_t index;
VkResult result = kk_query_table_add(dev, &h->table, gpu_id, &index);
if (result != VK_SUCCESS) {
mtl_release(handle);
ralloc_free(rc);
return result;
}
*rc = (struct kk_rc_sampler){
.key = desc,
.handle = handle,
.refcount = 1,
.index = index,
};
_mesa_hash_table_insert(h->ht, &rc->key, rc);
*out = rc;
return VK_SUCCESS;
}
VkResult
kk_sampler_heap_add(struct kk_device *dev, struct mtl_sampler_packed desc,
struct kk_rc_sampler **out)
{
struct kk_sampler_heap *h = &dev->samplers;
simple_mtx_lock(&h->lock);
VkResult result = kk_sampler_heap_add_locked(dev, h, desc, out);
simple_mtx_unlock(&h->lock);
return result;
}
static void
kk_sampler_heap_remove_locked(struct kk_device *dev, struct kk_sampler_heap *h,
struct kk_rc_sampler *rc)
{
assert(rc->refcount != 0);
rc->refcount--;
if (rc->refcount == 0) {
mtl_release(rc->handle);
kk_query_table_remove(dev, &h->table, rc->index);
_mesa_hash_table_remove_key(h->ht, &rc->key);
ralloc_free(rc);
}
}
void
kk_sampler_heap_remove(struct kk_device *dev, struct kk_rc_sampler *rc)
{
struct kk_sampler_heap *h = &dev->samplers;
simple_mtx_lock(&h->lock);
kk_sampler_heap_remove_locked(dev, h, rc);
simple_mtx_unlock(&h->lock);
}
static void
kk_parse_device_environment_options(struct kk_device *dev)
{
dev->gpu_capture_enabled =
debug_get_bool_option("MESA_KK_GPU_CAPTURE", false);
if (dev->gpu_capture_enabled) {
const char *capture_directory =
debug_get_option("MESA_KK_GPU_CAPTURE_DIRECTORY", NULL);
mtl_start_gpu_capture(dev->mtl_handle, capture_directory);
}
const char *list = debug_get_option("MESA_KK_DISABLE_WORKAROUNDS", "");
const char *all_workarounds = "all";
const size_t all_len = strlen(all_workarounds);
for (unsigned n; n = strcspn(list, ","), *list; list += MAX2(1, n)) {
if (n == all_len && !strncmp(list, all_workarounds, n)) {
dev->disabled_workarounds = UINT64_MAX;
break;
}
int index = atoi(list);
dev->disabled_workarounds |= BITFIELD64_BIT(index);
}
}
static VkResult
kk_get_timestamp(struct vk_device *device, uint64_t *timestamp)
{
struct kk_device *dev = container_of(device, struct kk_device, vk);
*timestamp = mtl_device_get_gpu_timestamp(dev->mtl_handle);
return VK_SUCCESS;
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_CreateDevice(VkPhysicalDevice physicalDevice,
const VkDeviceCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator, VkDevice *pDevice)
{
VK_FROM_HANDLE(kk_physical_device, pdev, physicalDevice);
VkResult result = VK_ERROR_OUT_OF_HOST_MEMORY;
struct kk_device *dev;
dev = vk_zalloc2(&pdev->vk.instance->alloc, pAllocator, sizeof(*dev), 8,
VK_SYSTEM_ALLOCATION_SCOPE_DEVICE);
if (!dev)
return vk_error(pdev, VK_ERROR_OUT_OF_HOST_MEMORY);
/* Fill the dispatch table we will expose to the users */
vk_device_dispatch_table_from_entrypoints(
&dev->exposed_dispatch_table, &vk_cmd_enqueue_device_entrypoints, true);
vk_device_dispatch_table_from_entrypoints(&dev->exposed_dispatch_table,
&kk_device_entrypoints, false);
vk_device_dispatch_table_from_entrypoints(&dev->exposed_dispatch_table,
&wsi_device_entrypoints, false);
vk_device_dispatch_table_from_entrypoints(
&dev->exposed_dispatch_table, &vk_common_device_entrypoints, false);
struct vk_device_dispatch_table dispatch_table;
vk_device_dispatch_table_from_entrypoints(&dispatch_table,
&kk_device_entrypoints, true);
vk_device_dispatch_table_from_entrypoints(
&dispatch_table, &vk_common_device_entrypoints, false);
vk_device_dispatch_table_from_entrypoints(&dispatch_table,
&wsi_device_entrypoints, false);
result = vk_device_init(&dev->vk, &pdev->vk, &dispatch_table, pCreateInfo,
pAllocator);
if (result != VK_SUCCESS)
goto fail_alloc;
dev->vk.shader_ops = &kk_device_shader_ops;
dev->mtl_handle = pdev->mtl_dev_handle;
dev->vk.command_buffer_ops = &kk_cmd_buffer_ops;
dev->vk.command_dispatch_table = &dev->vk.dispatch_table;
dev->vk.get_timestamp = kk_get_timestamp;
/* We need to initialize the device residency set before any bo is created. */
simple_mtx_init(&dev->residency_set.mutex, mtx_plain);
dev->residency_set.handle = mtl_new_residency_set(dev->mtl_handle);
if (dev->residency_set.handle == NULL)
goto fail_init;
if (pCreateInfo->queueCreateInfoCount > 0) {
result =
kk_queue_init(dev, &dev->queue, &pCreateInfo->pQueueCreateInfos[0], 0);
if (result != VK_SUCCESS)
goto fail_vab_memory;
dev->has_queue = true;
}
result = kk_device_init_meta(dev);
if (result != VK_SUCCESS)
goto fail_mem_cache;
result = kk_query_table_init(dev, &dev->occlusion_queries,
KK_MAX_OCCLUSION_QUERIES);
if (result != VK_SUCCESS)
goto fail_meta;
result = kk_init_sampler_heap(dev, &dev->samplers);
if (result != VK_SUCCESS)
goto fail_query_table;
result = kk_device_init_lib(dev);
if (result != VK_SUCCESS)
goto fail_sampler_heap;
kk_parse_device_environment_options(dev);
*pDevice = kk_device_to_handle(dev);
return VK_SUCCESS;
fail_sampler_heap:
kk_destroy_sampler_heap(dev, &dev->samplers);
fail_query_table:
kk_query_table_finish(dev, &dev->occlusion_queries);
fail_meta:
kk_device_finish_meta(dev);
fail_mem_cache:
if (dev->has_queue) {
kk_queue_finish(dev, &dev->queue);
dev->has_queue = false;
}
fail_vab_memory:
mtl_release(dev->residency_set.handle);
simple_mtx_destroy(&dev->residency_set.mutex);
fail_init:
vk_device_finish(&dev->vk);
fail_alloc:
vk_free(&dev->vk.alloc, dev);
return result;
}
VKAPI_ATTR void VKAPI_CALL
kk_DestroyDevice(VkDevice _device, const VkAllocationCallbacks *pAllocator)
{
VK_FROM_HANDLE(kk_device, dev, _device);
if (!dev)
return;
/* End capture before we start releasing resources. Otherwise, Metal capture
* may run into issues. */
if (dev->gpu_capture_enabled) {
mtl_stop_gpu_capture();
}
/* Meta first since it may destroy Vulkan objects */
kk_device_finish_meta(dev);
kk_device_finish_lib(dev);
kk_query_table_finish(dev, &dev->occlusion_queries);
kk_destroy_sampler_heap(dev, &dev->samplers);
/* Geometry heap */
if (dev->heap)
kk_destroy_bo(dev, dev->heap);
if (dev->has_queue) {
kk_queue_finish(dev, &dev->queue);
dev->has_queue = false;
}
/* Release the residency set last once all BOs are released. */
mtl_release(dev->residency_set.handle);
simple_mtx_destroy(&dev->residency_set.mutex);
vk_device_finish(&dev->vk);
vk_free(&dev->vk.alloc, dev);
}
/* We need to implement this ourselves so we give the fake ones for vk_common_*
* to work when executing actual commands */
static PFN_vkVoidFunction
kk_device_get_proc_addr(const struct kk_device *device, const char *name)
{
if (device == NULL || name == NULL)
return NULL;
struct vk_instance *instance = device->vk.physical->instance;
return vk_device_dispatch_table_get_if_supported(
&device->exposed_dispatch_table, name, instance->app_info.api_version,
&instance->enabled_extensions, &device->vk.enabled_extensions);
}
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
kk_GetDeviceProcAddr(VkDevice _device, const char *pName)
{
VK_FROM_HANDLE(kk_device, device, _device);
return kk_device_get_proc_addr(device, pName);
}
void
kk_device_add_heap_to_residency_set(struct kk_device *dev, mtl_heap *heap)
{
simple_mtx_lock(&dev->residency_set.mutex);
mtl_residency_set_add_allocation(dev->residency_set.handle, heap);
simple_mtx_unlock(&dev->residency_set.mutex);
}
void
kk_device_remove_heap_from_residency_set(struct kk_device *dev, mtl_heap *heap)
{
simple_mtx_lock(&dev->residency_set.mutex);
mtl_residency_set_remove_allocation(dev->residency_set.handle, heap);
simple_mtx_unlock(&dev->residency_set.mutex);
}
void
kk_device_add_buffer_to_residency_set(struct kk_device *dev, mtl_buffer *buffer)
{
simple_mtx_lock(&dev->residency_set.mutex);
mtl_residency_set_add_allocation(dev->residency_set.handle, buffer);
simple_mtx_unlock(&dev->residency_set.mutex);
}
void
kk_device_remove_buffer_from_residency_set(struct kk_device *dev,
mtl_buffer *buffer)
{
simple_mtx_lock(&dev->residency_set.mutex);
mtl_residency_set_remove_allocation(dev->residency_set.handle, buffer);
simple_mtx_unlock(&dev->residency_set.mutex);
}
void
kk_device_make_resources_resident(struct kk_device *dev)
{
simple_mtx_lock(&dev->residency_set.mutex);
mtl_residency_set_commit(dev->residency_set.handle);
mtl_residency_set_request_residency(dev->residency_set.handle);
simple_mtx_unlock(&dev->residency_set.mutex);
}
+138
View File
@@ -0,0 +1,138 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_DEVICE_H
#define KK_DEVICE_H 1
#include "kk_private.h"
#include "kk_query_table.h"
#include "kk_queue.h"
#include "kosmickrisp/bridge/mtl_types.h"
#include "kosmickrisp/clc/kk_precompiled_shader.h"
#include "libkk_shaders.h"
#include "util/u_dynarray.h"
#include "vk_device.h"
#include "vk_meta.h"
#include "vk_queue.h"
struct kk_bo;
struct kk_physical_device;
struct vk_pipeline_cache;
struct kk_residency_set {
simple_mtx_t mutex;
mtl_residency_set *handle;
};
struct mtl_sampler_packed {
enum mtl_sampler_address_mode mode_u;
enum mtl_sampler_address_mode mode_v;
enum mtl_sampler_address_mode mode_w;
enum mtl_sampler_border_color border_color;
enum mtl_sampler_min_mag_filter min_filter;
enum mtl_sampler_min_mag_filter mag_filter;
enum mtl_sampler_mip_filter mip_filter;
enum mtl_compare_function compare_func;
float min_lod;
float max_lod;
uint32_t max_anisotropy;
bool normalized_coordinates;
};
struct kk_rc_sampler {
struct mtl_sampler_packed key;
mtl_sampler *handle;
/* Reference count for this hardware sampler, protected by the heap mutex */
uint16_t refcount;
/* Index of this hardware sampler in the hardware sampler heap */
uint16_t index;
};
struct kk_sampler_heap {
simple_mtx_t lock;
struct kk_query_table table;
/* Map of mtl_sampler_packed to kk_rc_sampler */
struct hash_table *ht;
};
struct kk_precompiled_cache {
struct kk_precompiled_shader shaders[LIBKK_NUM_PROGRAMS];
};
struct kk_device {
struct vk_device vk;
mtl_device *mtl_handle;
/* Dispatch table exposed to the user. Required since we need to record all
* commands due to Metal limitations */
struct vk_device_dispatch_table exposed_dispatch_table;
struct kk_sampler_heap samplers;
struct kk_query_table occlusion_queries;
/* Track all heaps the user allocated so we can set them all as resident when
* recording as required by Metal. */
struct kk_residency_set residency_set;
struct kk_precompiled_cache precompiled_cache;
bool has_queue;
struct kk_queue queue;
struct vk_meta_device meta;
/* Geomtry heap */
struct kk_bo *heap;
util_once_flag heap_init_once;
uint64_t disabled_workarounds;
bool gpu_capture_enabled;
};
VK_DEFINE_HANDLE_CASTS(kk_device, vk.base, VkDevice, VK_OBJECT_TYPE_DEVICE)
static inline struct kk_physical_device *
kk_device_physical(const struct kk_device *dev)
{
return (struct kk_physical_device *)dev->vk.physical;
}
VkResult kk_device_init_meta(struct kk_device *dev);
void kk_device_finish_meta(struct kk_device *dev);
VkResult kk_device_init_lib(struct kk_device *dev);
void kk_device_finish_lib(struct kk_device *dev);
void kk_device_add_heap_to_residency_set(struct kk_device *dev, mtl_heap *heap);
void kk_device_remove_heap_from_residency_set(struct kk_device *dev,
mtl_heap *heap);
void kk_device_add_buffer_to_residency_set(struct kk_device *dev,
mtl_buffer *buffer);
void kk_device_remove_buffer_from_residency_set(struct kk_device *dev,
mtl_buffer *buffer);
void kk_device_make_resources_resident(struct kk_device *dev);
/* Required to create a sampler */
mtl_sampler *kk_sampler_create(struct kk_device *dev,
const struct mtl_sampler_packed *packed);
VkResult kk_sampler_heap_add(struct kk_device *dev,
struct mtl_sampler_packed desc,
struct kk_rc_sampler **out);
void kk_sampler_heap_remove(struct kk_device *dev, struct kk_rc_sampler *rc);
#endif // KK_DEVICE_H
+71
View File
@@ -0,0 +1,71 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_device.h"
#include "kk_shader.h"
#include "kosmickrisp/clc/kk_precompiled_shader.h"
#include "libkk_shaders.h"
#include "kosmickrisp/bridge/mtl_bridge.h"
#include "nir/nir.h"
#include "nir/nir_builder.h"
static VkResult
build_precompiled_shaders(struct kk_device *dev)
{
uint32_t i = 0u;
for (; i < LIBKK_NUM_PROGRAMS; ++i) {
const uint32_t *bin = libkk_AppleSilicon[i];
const struct kk_precompiled_info *info = (void *)bin;
const char *msl = (const char *)bin + sizeof(*info);
mtl_library *library = mtl_new_library(dev->mtl_handle, msl);
if (library == NULL)
goto fail;
struct kk_precompiled_shader *shader = &dev->precompiled_cache.shaders[i];
/* TODO_KOSMICKRISP Do not hardcode the entrypoint */
mtl_function *function =
mtl_new_function_with_name(library, "main_entrypoint");
uint32_t local_size_threads = info->workgroup_size[0] *
info->workgroup_size[1] *
info->workgroup_size[2];
shader->pipeline = mtl_new_compute_pipeline_state(
dev->mtl_handle, function, local_size_threads);
mtl_release(function);
mtl_release(library);
if (!shader->pipeline)
goto fail;
memcpy(&shader->info, info, sizeof(*info));
}
return VK_SUCCESS;
fail:
for (uint32_t j = 0u; j < i; ++j)
mtl_release(dev->precompiled_cache.shaders[j].pipeline);
return vk_error(dev, VK_ERROR_INVALID_SHADER_NV);
}
VkResult
kk_device_init_lib(struct kk_device *dev)
{
return build_precompiled_shaders(dev);
}
void
kk_device_finish_lib(struct kk_device *dev)
{
for (uint32_t i = 0; i < LIBKK_NUM_PROGRAMS; ++i)
mtl_release(dev->precompiled_cache.shaders[i].pipeline);
}
Loaded 100 of 12511 files, more files were not shown because too many files have changed in this diff. Show more