kk: Add KosmicKrisp
Some checks are pending
macOS-CI / macOS-CI (dri) (push) Waiting to run
macOS-CI / macOS-CI (xlib) (push) Waiting to run

Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/37522>
This commit is contained in:
Aitor Camacho 2025-09-23 10:24:52 +02:00 committed by Marge Bot
parent f6c7f16322
commit 7c268a1e91
130 changed files with 24271 additions and 2 deletions

View file

@ -271,7 +271,7 @@ elif _vulkan_drivers.contains('all')
_vulkan_drivers = ['amd', 'intel', 'intel_hasvk', 'swrast',
'freedreno', 'panfrost', 'virtio', 'broadcom',
'imagination', 'microsoft-experimental',
'nouveau', 'asahi', 'gfxstream']
'nouveau', 'asahi', 'gfxstream', 'kosmickrisp']
endif
with_intel_vk = _vulkan_drivers.contains('intel')
@ -288,6 +288,7 @@ with_microsoft_vk = _vulkan_drivers.contains('microsoft-experimental')
with_nouveau_vk = _vulkan_drivers.contains('nouveau')
with_asahi_vk = _vulkan_drivers.contains('asahi')
with_gfxstream_vk = _vulkan_drivers.contains('gfxstream')
with_kosmickrisp_vk = _vulkan_drivers.contains('kosmickrisp')
with_any_vk = _vulkan_drivers.length() != 0
with_llvm = with_llvm \
@ -829,6 +830,7 @@ with_driver_using_cl = [
with_gallium_asahi, with_asahi_vk, with_tools.contains('asahi'),
with_gallium_panfrost, with_panfrost_vk,
with_nouveau_vk, with_imagination_vk,
with_kosmickrisp_vk,
].contains(true)
if get_option('mesa-clc') == 'system'

View file

@ -209,7 +209,7 @@ option(
choices : ['auto', 'amd', 'broadcom', 'freedreno', 'intel', 'intel_hasvk',
'panfrost', 'swrast', 'virtio', 'imagination',
'microsoft-experimental', 'nouveau', 'asahi', 'gfxstream',
'all'],
'kosmickrisp', 'all'],
description : 'List of vulkan drivers to build. If this is set to auto ' +
'all drivers applicable to the target OS/architecture ' +
'will be built'

View file

@ -0,0 +1,7 @@
BasedOnStyle: InheritParentConfig
DisableFormat: false
AlignConsecutiveBitFields: true
ColumnLimit: 80
BreakStringLiterals: false
SpaceBeforeParens: ControlStatementsExceptControlMacros

View file

@ -0,0 +1,61 @@
# 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_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_sampler.c',
'stubs/mtl_sync.c',
'stubs/mtl_texture.c',
)
endif
mtl_bridge_dependencies = [
idep_vulkan_lite_runtime_headers,
idep_vulkan_util_headers
]
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,
)

View file

@ -0,0 +1,43 @@
/*
* 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_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 */

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];
}
}

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 */

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;
}
}

View file

@ -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 */

View file

@ -0,0 +1,64 @@
/*
* 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) {
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];
}
}

View file

@ -0,0 +1,19 @@
/*
* 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);
#endif /* MTL_COMMAND_QUEUE_H */

View file

@ -0,0 +1,28 @@
/*
* 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];
}
}

View file

@ -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);

View file

@ -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;
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef MTL_DEVICE_H
#define MTL_DEVICE_H 1
#include "mtl_types.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);
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);
struct mtl_size mtl_device_max_threads_per_threadgroup(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);
void
mtl_heap_texture_size_and_align_with_descriptor(mtl_device *device,
struct kk_image_layout *layout);
#endif /* MTL_DEVICE_H */

View file

@ -0,0 +1,197 @@
/*
* 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();
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];
}
[devs[i] autorelease];
}
}
return device;
}
}
/* Device operations */
void
mtl_start_gpu_capture(mtl_device *mtl_dev_handle)
{
@autoreleasepool {
id<MTLDevice> mtl_dev = (id<MTLDevice>)mtl_dev_handle;
MTLCaptureManager *captureMgr = [MTLCaptureManager sharedCaptureManager];
// Before macOS 10.15 and iOS 13.0, captureDesc will just be nil
MTLCaptureDescriptor *captureDesc = [[MTLCaptureDescriptor new] autorelease];
captureDesc.captureObject = mtl_dev;
captureDesc.destination = MTLCaptureDestinationDeveloperTools;
// TODO_KOSMICKRISP Support dumping a trace to a file?
// NSString *tmp_dir = NSTemporaryDirectory();
// NSString *pname = [[NSProcessInfo processInfo] processName];
// NSString *capture_path = [NSString stringWithFormat:@"%@/%@.gputrace", tmp_dir, pname];
// if ([captureMgr supportsDestination: MTLCaptureDestinationGPUTraceDocument] ) {
// 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);
// fprintf(stderr, "Using startCaptureWithDevice\n");
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[captureMgr startCaptureWithDevice:mtl_dev];
#pragma clang diagnostic pop
}
//[tmp_dir release];
//[pname release];
//[capture_path release];
}
}
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;
}
}
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};
}
}
/* 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;
}
}
void
mtl_heap_texture_size_and_align_with_descriptor(mtl_device *device,
struct kk_image_layout *layout)
{
@autoreleasepool {
id<MTLDevice> dev = (id<MTLDevice>)device;
if (layout->optimized_layout) {
MTLTextureDescriptor *descriptor = [mtl_new_texture_descriptor(layout) autorelease];
descriptor.resourceOptions = KK_MTL_RESOURCE_OPTIONS;
MTLSizeAndAlign size_align = [dev heapTextureSizeAndAlignWithDescriptor:descriptor];
layout->size_B = size_align.size;
layout->align_B = size_align.align;
} else {
/* Linear textures have different alignment since they are allocated on top of MTLBuffers */
layout->align_B = [dev minimumLinearTextureAlignmentForPixelFormat:layout->format.mtl];
}
}
}

View file

@ -0,0 +1,152 @@
/*
* 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_resource(mtl_compute_encoder *encoder,
mtl_resource *res_handle, uint32_t usage);
void mtl_compute_use_resources(mtl_compute_encoder *encoder,
mtl_resource **resource_handles, uint32_t count,
enum mtl_resource_usage usage);
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);
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_fragment_buffer(mtl_render_encoder *encoder, mtl_buffer *buffer,
uint32_t offset, 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 */

View file

@ -0,0 +1,537 @@
/*
* 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_resource(mtl_compute_encoder *encoder,
mtl_resource *res_handle, uint32_t usage)
{
@autoreleasepool {
id<MTLComputeCommandEncoder> enc = (id<MTLComputeCommandEncoder>)encoder;
id<MTLResource> res = (id<MTLResource>)res_handle;
[enc useResource:res usage:(MTLResourceUsage)usage];
}
}
void
mtl_compute_use_resources(mtl_compute_encoder *encoder,
mtl_resource **resource_handles, uint32_t count,
enum mtl_resource_usage usage)
{
@autoreleasepool {
id<MTLComputeCommandEncoder> enc = (id<MTLComputeCommandEncoder>)encoder;
id<MTLResource> *handles = (id<MTLResource>*)resource_handles;
[enc useResources:handles count:count usage:(MTLResourceUsage)usage];
}
}
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 * local_size.x,
grid_size.y * local_size.y,
grid_size.z * local_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];
}
}
/* 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_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_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];
}
}

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_BC1_RGBA = 130,
MTL_PIXEL_FORMAT_BC1_RGBA_SRGB = 131,
MTL_PIXEL_FORMAT_BC2_RGBA = 132,
MTL_PIXEL_FORMAT_BC2_RGBA_SRGB = 133,
MTL_PIXEL_FORMAT_BC3_RGBA = 134,
MTL_PIXEL_FORMAT_BC3_RGBA_SRGB = 135,
/* RGTC */
MTL_PIXEL_FORMAT_BC4_R_UNORM = 140,
MTL_PIXEL_FORMAT_BC4_R_SNORM = 141,
MTL_PIXEL_FORMAT_BC5_RG_UNORM = 142,
MTL_PIXEL_FORMAT_BC5_RG_SNORM = 143,
/* BPTC */
MTL_PIXEL_FORMAT_BC6H_RGB_FLOAT = 150,
MTL_PIXEL_FORMAT_BC6H_RGBU_FLOAT = 151,
MTL_PIXEL_FORMAT_BC7_RGBA_UNORM = 152,
MTL_PIXEL_FORMAT_BC7_RGBA_SRGB = 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_HDR = 222,
MTL_PIXEL_FORMAT_ASTC_5x4_HDR = 223,
MTL_PIXEL_FORMAT_ASTC_5x5_HDR = 224,
MTL_PIXEL_FORMAT_ASTC_6x5_HDR = 225,
MTL_PIXEL_FORMAT_ASTC_6x6_HDR = 226,
MTL_PIXEL_FORMAT_ASTC_8x5_HDR = 228,
MTL_PIXEL_FORMAT_ASTC_8x6_HDR = 229,
MTL_PIXEL_FORMAT_ASTC_8x8_HDR = 230,
MTL_PIXEL_FORMAT_ASTC_10x5_HDR = 231,
MTL_PIXEL_FORMAT_ASTC_10x6_HDR = 232,
MTL_PIXEL_FORMAT_ASTC_10x8_HDR = 233,
MTL_PIXEL_FORMAT_ASTC_10x10_HDR = 234,
MTL_PIXEL_FORMAT_ASTC_12x10_HDR = 235,
MTL_PIXEL_FORMAT_ASTC_12x12_HDR = 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 */

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 */

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];
}
}

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 */

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];
}
}

View file

@ -0,0 +1,165 @@
/*
* 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_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 */

View file

@ -0,0 +1,475 @@
/*
* 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_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];
}
}

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 */

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;
}
}

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 */

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;
}
}

View file

@ -0,0 +1,27 @@
/*
* 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);
#endif /* MTL_TEXTURE_H */

View file

@ -0,0 +1,94 @@
/*
* 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];
}
}

View file

@ -0,0 +1,272 @@
/*
* 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;
/** 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,
};
/** 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_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;
};
#endif /* KK_MTL_TYPES_H */

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)
{
}

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;
}

View file

@ -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)
{
}

View file

@ -0,0 +1,19 @@
/*
* 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;
}

View file

@ -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;
}

View file

@ -0,0 +1,73 @@
/*
* 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)
{
}
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;
}
struct mtl_size
mtl_device_max_threads_per_threadgroup(mtl_device *dev)
{
return (struct mtl_size){};
}
/* Resource queries */
void
mtl_heap_buffer_size_and_align_with_length(mtl_device *device, uint64_t *size_B,
uint64_t *align_B)
{
}
void
mtl_heap_texture_size_and_align_with_descriptor(mtl_device *device,
struct kk_image_layout *layout)
{
}

View file

@ -0,0 +1,273 @@
/*
* 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)
{
}
/* 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_resource(mtl_compute_encoder *encoder, mtl_resource *res_handle,
uint32_t usage)
{
}
void
mtl_compute_use_resources(mtl_compute_encoder *encoder,
mtl_resource **resource_handles, uint32_t count,
enum mtl_resource_usage usage)
{
}
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_fragment_buffer(mtl_render_encoder *encoder, mtl_buffer *buffer,
uint32_t offset, 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)
{
}

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;
}

View file

@ -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;
}

View file

@ -0,0 +1,288 @@
/*
* 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_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;
}

View file

@ -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;
}

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)
{
}

View file

@ -0,0 +1,29 @@
/*
* 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;
}

View file

@ -0,0 +1,251 @@
/*
* 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:
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");
}
}

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
enum pipe_format;
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);
#endif /* KK_MTL_TO_VK_MAP_H */

View file

@ -0,0 +1,35 @@
# 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_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,
)

View file

@ -0,0 +1,447 @@
/*
* 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 char *VARYING_SLOT_NAME[NUM_TOTAL_VARYING_SLOTS] = {
[VARYING_SLOT_POS] = "position",
[VARYING_SLOT_PSIZ] = "point_size",
[VARYING_SLOT_PRIMITIVE_ID] = "primitive_id",
[VARYING_SLOT_LAYER] = "layer",
[VARYING_SLOT_VAR0] = "vary_00",
[VARYING_SLOT_VAR1] = "vary_01",
[VARYING_SLOT_VAR2] = "vary_02",
[VARYING_SLOT_VAR3] = "vary_03",
[VARYING_SLOT_VAR4] = "vary_04",
[VARYING_SLOT_VAR5] = "vary_05",
[VARYING_SLOT_VAR6] = "vary_06",
[VARYING_SLOT_VAR7] = "vary_07",
[VARYING_SLOT_VAR8] = "vary_08",
[VARYING_SLOT_VAR9] = "vary_09",
[VARYING_SLOT_VAR10] = "vary_10",
[VARYING_SLOT_VAR11] = "vary_11",
[VARYING_SLOT_VAR12] = "vary_12",
[VARYING_SLOT_VAR13] = "vary_13",
[VARYING_SLOT_VAR14] = "vary_14",
[VARYING_SLOT_VAR15] = "vary_15",
[VARYING_SLOT_VAR16] = "vary_16",
[VARYING_SLOT_VAR17] = "vary_17",
[VARYING_SLOT_VAR18] = "vary_18",
[VARYING_SLOT_VAR19] = "vary_19",
[VARYING_SLOT_VAR20] = "vary_20",
[VARYING_SLOT_VAR21] = "vary_21",
[VARYING_SLOT_VAR22] = "vary_22",
[VARYING_SLOT_VAR23] = "vary_23",
[VARYING_SLOT_VAR24] = "vary_24",
[VARYING_SLOT_VAR25] = "vary_25",
[VARYING_SLOT_VAR26] = "vary_26",
[VARYING_SLOT_VAR27] = "vary_27",
[VARYING_SLOT_VAR28] = "vary_28",
[VARYING_SLOT_VAR29] = "vary_29",
[VARYING_SLOT_VAR30] = "vary_30",
[VARYING_SLOT_VAR31] = "vary_31",
};
/* Mapping from NIR varying slot to the MSL struct member attribute. */
static const char *VARYING_SLOT_SEMANTIC[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_VAR0] = "[[user(vary_00)]]",
[VARYING_SLOT_VAR1] = "[[user(vary_01)]]",
[VARYING_SLOT_VAR2] = "[[user(vary_02)]]",
[VARYING_SLOT_VAR3] = "[[user(vary_03)]]",
[VARYING_SLOT_VAR4] = "[[user(vary_04)]]",
[VARYING_SLOT_VAR5] = "[[user(vary_05)]]",
[VARYING_SLOT_VAR6] = "[[user(vary_06)]]",
[VARYING_SLOT_VAR7] = "[[user(vary_07)]]",
[VARYING_SLOT_VAR8] = "[[user(vary_08)]]",
[VARYING_SLOT_VAR9] = "[[user(vary_09)]]",
[VARYING_SLOT_VAR10] = "[[user(vary_10)]]",
[VARYING_SLOT_VAR11] = "[[user(vary_11)]]",
[VARYING_SLOT_VAR12] = "[[user(vary_12)]]",
[VARYING_SLOT_VAR13] = "[[user(vary_13)]]",
[VARYING_SLOT_VAR14] = "[[user(vary_14)]]",
[VARYING_SLOT_VAR15] = "[[user(vary_15)]]",
[VARYING_SLOT_VAR16] = "[[user(vary_16)]]",
[VARYING_SLOT_VAR17] = "[[user(vary_17)]]",
[VARYING_SLOT_VAR18] = "[[user(vary_18)]]",
[VARYING_SLOT_VAR19] = "[[user(vary_19)]]",
[VARYING_SLOT_VAR20] = "[[user(vary_20)]]",
[VARYING_SLOT_VAR21] = "[[user(vary_21)]]",
[VARYING_SLOT_VAR22] = "[[user(vary_22)]]",
[VARYING_SLOT_VAR23] = "[[user(vary_23)]]",
[VARYING_SLOT_VAR24] = "[[user(vary_24)]]",
[VARYING_SLOT_VAR25] = "[[user(vary_25)]]",
[VARYING_SLOT_VAR26] = "[[user(vary_26)]]",
[VARYING_SLOT_VAR27] = "[[user(vary_27)]]",
[VARYING_SLOT_VAR28] = "[[user(vary_28)]]",
[VARYING_SLOT_VAR29] = "[[user(vary_29)]]",
[VARYING_SLOT_VAR30] = "[[user(vary_30)]]",
[VARYING_SLOT_VAR31] = "[[user(vary_31)]]",
};
/* 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];
const char *type = alu_type_to_string(info.type);
const char *vector_suffix = vector_suffixes[info.num_components];
P_IND(ctx, "%s%s %s %s;\n", type, vector_suffix,
VARYING_SLOT_NAME[location], VARYING_SLOT_SEMANTIC[location]);
}
ctx->indentlevel--;
P(ctx, "};\n");
}
/* 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];
const char *type = alu_type_to_string(info.type);
const char *vector_suffix = vector_suffixes[info.num_components];
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;
}
P_IND(ctx, "%s%s %s %s %s;\n", type, vector_suffix,
VARYING_SLOT_NAME[location], VARYING_SLOT_SEMANTIC[location],
interp);
}
/* 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 {
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;
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:
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;
}
}
const char *
msl_output_name(struct nir_to_msl_ctx *ctx, unsigned location)
{
switch (ctx->shader->info.stage) {
case MESA_SHADER_VERTEX:
return VARYING_SLOT_NAME[location];
case MESA_SHADER_FRAGMENT:
return FS_OUTPUT_NAME[location];
default:
assert(0);
return "";
}
}
const char *
msl_input_name(struct nir_to_msl_ctx *ctx, unsigned location)
{
switch (ctx->shader->info.stage) {
case MESA_SHADER_FRAGMENT:
return VARYING_SLOT_NAME[location];
default:
assert(0);
return "";
}
}

View file

@ -0,0 +1,38 @@
# 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'
lower_pack = [
# 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))),
]
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_pack).render())
if __name__ == '__main__':
main()

View file

@ -0,0 +1,255 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#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;
}
bool
msl_nir_fs_force_output_signedness(
nir_shader *nir, enum pipe_format render_target_formats[MAX_DRAW_BUFFERS])
{
assert(nir->info.stage == MESA_SHADER_FRAGMENT);
bool update_derefs = false;
nir_foreach_variable_with_modes(var, nir, nir_var_shader_out) {
if (FRAG_RESULT_DATA0 <= var->data.location &&
var->data.location <= FRAG_RESULT_DATA7 &&
glsl_type_is_integer(var->type)) {
unsigned int slot = var->data.location - FRAG_RESULT_DATA0;
if (glsl_type_is_uint_16_32_64(var->type) &&
util_format_is_pure_sint(render_target_formats[slot])) {
var->type = glsl_ivec_type(var->type->vector_elements);
update_derefs = true;
} else if (glsl_type_is_int_16_32_64(var->type) &&
util_format_is_pure_uint(render_target_formats[slot])) {
var->type = glsl_uvec_type(var->type->vector_elements);
update_derefs = true;
}
}
}
if (update_derefs) {
nir_foreach_function_impl(impl, nir) {
nir_foreach_block(block, impl) {
nir_foreach_instr(instr, block) {
switch (instr->type) {
case nir_instr_type_deref: {
nir_deref_instr *deref = nir_instr_as_deref(instr);
if (deref->deref_type == nir_deref_type_var) {
deref->type = deref->var->type;
}
break;
}
default:
break;
}
}
}
nir_progress(update_derefs, impl, nir_metadata_control_flow);
}
}
return update_derefs;
}
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;
nir_def *sample_mask = (nir_def *)data;
nir_def_rewrite_uses(&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 *sample_mask_def = nir_imm_int(&b, sample_mask);
nir_store_output(&b, sample_mask_def, nir_imm_int(&b, 0u), .base = 0u,
.range = 1u, .write_mask = 0x1, .component = 0u,
.src_type = nir_type_uint32, .io_semantics = io_semantics);
return nir_shader_intrinsics_pass(
nir, msl_replace_load_sample_mask_in_for_static_sample_mask,
nir_metadata_control_flow, sample_mask_def);
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_load_frag_coord(&b);
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) {
nir_variable *position_var = nir_create_variable_with_location(
nir, nir_var_shader_out, VARYING_SLOT_POS, glsl_vec4_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 *position_deref = nir_build_deref_var(&b, position_var);
nir_def *zero = nir_imm_float(&b, 0.0f);
nir_store_deref(&b, position_deref, nir_vec4(&b, zero, zero, zero, zero),
0xFFFFFFFF);
nir->info.outputs_written |= BITFIELD64_BIT(VARYING_SLOT_POS);
return nir_progress(true, entrypoint, nir_metadata_control_flow);
}
return false;
}
static bool
msl_sample_mask_uint(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 == FRAG_RESULT_SAMPLE_MASK)
nir_intrinsic_set_src_type(intr, nir_type_uint32);
}
return false;
}
bool
msl_nir_sample_mask_type(nir_shader *nir)
{
assert(nir->info.stage == MESA_SHADER_FRAGMENT);
return nir_shader_intrinsics_pass(nir, msl_sample_mask_uint,
nir_metadata_all, NULL);
}
static bool
msl_layer_id_uint(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)
nir_intrinsic_set_src_type(intr, nir_type_uint32);
}
return false;
}
bool
msl_nir_layer_id_type(nir_shader *nir)
{
assert(nir->info.stage == MESA_SHADER_VERTEX);
return nir_shader_intrinsics_pass(nir, msl_layer_id_uint, nir_metadata_all,
NULL);
}

View file

@ -0,0 +1,98 @@
/*
* 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_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;
}
static bool
lower(nir_builder *b, nir_intrinsic_instr *intr, void *data)
{
b->cursor = nir_before_instr(&intr->instr);
switch (intr->intrinsic) {
case nir_intrinsic_vote_any: {
/* We don't have vote instructions, but we have efficient ballots */
nir_def *ballot = nir_ballot(b, 1, 32, intr->src[0].ssa);
nir_def_rewrite_uses(&intr->def, nir_ine_imm(b, ballot, 0));
return true;
}
case nir_intrinsic_vote_all: {
nir_def *ballot = nir_ballot(b, 1, 32, nir_inot(b, intr->src[0].ssa));
nir_def_rewrite_uses(&intr->def, nir_ieq_imm(b, ballot, 0));
return true;
}
default:
return false;
}
}
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,
.lower_relative_shuffle = true,
.lower_quad = true,
.lower_reduce = true,
};
NIR_PASS(_, nir, nir_lower_subgroups, &subgroups_options);
NIR_PASS(_, nir, nir_shader_intrinsics_pass, lower,
nir_metadata_control_flow, NULL);
NIR_PASS(_, nir, nir_shader_intrinsics_pass, lower_bool_ops,
nir_metadata_control_flow, NULL);
}

View file

@ -0,0 +1,77 @@
/*
* 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;
};
struct nir_to_msl_ctx {
FILE *output;
struct hash_table *types;
nir_shader *shader;
struct _mesa_string_buffer *text;
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);
const char *msl_input_name(struct nir_to_msl_ctx *ctx, unsigned location);
const char *msl_output_name(struct nir_to_msl_ctx *ctx, unsigned 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);

View file

@ -0,0 +1,857 @@
/*
* 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:
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_broadcast:
case nir_intrinsic_quad_swap_horizontal:
case nir_intrinsic_quad_swap_vertical:
case nir_intrinsic_quad_swap_diagonal:
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_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_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_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:
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_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:
set_type(types, &instr->src[0], TYPE_FLOAT);
set_type(types, &instr->def, TYPE_FLOAT);
break;
case nir_intrinsic_load_point_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_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 = alu->src[i].src.ssa->parent_instr;
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 = intr->src[i].ssa->parent_instr;
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 = tex->src[i].src.ssa->parent_instr;
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 (isinf(v)) {
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 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

View file

@ -0,0 +1,56 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "nir.h"
enum pipe_format;
/* Assumes nir_shader_gather_info has been called beforehand. */
char *nir_to_msl(nir_shader *shader, void *mem_ctx);
/* 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);
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)
{
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_nir_sample_mask_type(nir_shader *nir);
bool msl_nir_layer_id_type(nir_shader *nir);

187
src/kosmickrisp/kosmicomp.c Normal file
View file

@ -0,0 +1,187 @@
/*
* 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 %ld %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);
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), true, false);
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, 0, 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);
char *msl_text = nir_to_msl(shader, shader);
fputs(msl_text, stdout);
ralloc_free(msl_text);
return 0;
}

View file

@ -0,0 +1,16 @@
# Copyright 2025 LunarG, Inc.
# Copyright 2025 Google LLC
# SPDX-License-Identifier: MIT
subdir('bridge')
subdir('compiler')
subdir('util')
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'],
)

View file

@ -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()

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,
)

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

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"
)

View file

@ -0,0 +1,50 @@
/*
* 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"
void
libkk_write_u64(global struct libkk_imm_write *write_array)
{
*write_array[cl_group_id.x].address = write_array[cl_group_id.x].value;
}
void
libkk_copy_queries(global uint64_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);
}
}

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(uint64_t) address;
uint64_t value;
};
#define LIBKK_QUERY_UNAVAILABLE (uint64_t)((int64_t)-1)
#endif /* KK_QUERY_H */

View file

@ -0,0 +1,283 @@
/*
* Copyright 2023 Alyssa Rosenzweig
* Copyright 2023 Valve Corporation
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "compiler/libcl/libcl_vk.h"
#include "compiler/shader_enums.h"
static uint
libkk_vertex_id_for_line_loop(uint prim, uint vert, uint num_prims)
{
/* (0, 1), (1, 2), (2, 0) */
if (prim == (num_prims - 1) && vert == 1)
return 0;
else
return prim + vert;
}
/* Swap the two non-provoking vertices third vert in odd triangles. This
* generates a vertex ID list with a consistent winding order.
*
* With prim and flatshade_first, the map : [0, 1, 2] -> [0, 1, 2] is its own
* inverse. This lets us reuse it for both vertex fetch and transform feedback.
*/
static uint
libagx_map_vertex_in_tri_strip(uint prim, uint vert, bool flatshade_first)
{
unsigned pv = flatshade_first ? 0 : 2;
bool even = (prim & 1) == 0;
bool provoking = vert == pv;
return (provoking || even) ? vert : ((3 - pv) - vert);
}
static uint
libkk_vertex_id_for_tri_fan(uint prim, uint vert, bool flatshade_first)
{
/* Vulkan spec section 20.1.7 gives (i + 1, i + 2, 0) for a provoking
* first. OpenGL instead wants (0, i + 1, i + 2) with a provoking last.
* Piglit clipflat expects us to switch between these orders depending on
* provoking vertex, to avoid trivializing the fan.
*
* Rotate accordingly.
*/
if (flatshade_first) {
vert = (vert == 2) ? 0 : (vert + 1);
}
/* The simpler form assuming last is provoking. */
return (vert == 0) ? 0 : prim + vert;
}
static uint
libkk_vertex_id_for_tri_strip_adj(uint prim, uint vert, uint num_prims,
bool flatshade_first)
{
/* See Vulkan spec section 20.1.11 "Triangle Strips With Adjancency".
*
* There are different cases for first/middle/last/only primitives and for
* odd/even primitives. Determine which case we're in.
*/
bool last = prim == (num_prims - 1);
bool first = prim == 0;
bool even = (prim & 1) == 0;
bool even_or_first = even || first;
/* When the last vertex is provoking, we rotate the primitives
* accordingly. This seems required for OpenGL.
*/
if (!flatshade_first && !even_or_first) {
vert = (vert + 4u) % 6u;
}
/* Offsets per the spec. The spec lists 6 cases with 6 offsets. Luckily,
* there are lots of patterns we can exploit, avoiding a full 6x6 LUT.
*
* Here we assume the first vertex is provoking, the Vulkan default.
*/
uint offsets[6] = {
0,
first ? 1 : (even ? -2 : 3),
even_or_first ? 2 : 4,
last ? 5 : 6,
even_or_first ? 4 : 2,
even_or_first ? 3 : -2,
};
/* Ensure NIR can see thru the local array */
uint offset = 0;
for (uint i = 1; i < 6; ++i) {
if (i == vert)
offset = offsets[i];
}
/* Finally add to the base of the primitive */
return (prim * 2) + offset;
}
static uint
vertex_id_for_topology(enum mesa_prim mode, bool flatshade_first, uint prim,
uint vert, uint num_prims)
{
switch (mode) {
case MESA_PRIM_POINTS:
case MESA_PRIM_LINES:
case MESA_PRIM_TRIANGLES:
case MESA_PRIM_LINES_ADJACENCY:
case MESA_PRIM_TRIANGLES_ADJACENCY:
/* Regular primitive: every N vertices defines a primitive */
return (prim * mesa_vertices_per_prim(mode)) + vert;
case MESA_PRIM_LINE_LOOP:
return libkk_vertex_id_for_line_loop(prim, vert, num_prims);
case MESA_PRIM_LINE_STRIP:
case MESA_PRIM_LINE_STRIP_ADJACENCY:
/* (i, i + 1) or (i, ..., i + 3) */
return prim + vert;
case MESA_PRIM_TRIANGLE_STRIP: {
/* Order depends on the provoking vert.
*
* First: (0, 1, 2), (1, 3, 2), (2, 3, 4).
* Last: (0, 1, 2), (2, 1, 3), (2, 3, 4).
*
* Pull the (maybe swapped) vert from the corresponding primitive
*/
return prim + libagx_map_vertex_in_tri_strip(prim, vert, flatshade_first);
}
case MESA_PRIM_TRIANGLE_FAN:
return libkk_vertex_id_for_tri_fan(prim, vert, flatshade_first);
case MESA_PRIM_TRIANGLE_STRIP_ADJACENCY:
return libkk_vertex_id_for_tri_strip_adj(prim, vert, num_prims,
flatshade_first);
default:
return 0;
}
}
static void
store_index(global uint8_t *index_buffer, uint index_size_B, uint id,
uint value)
{
global uint32_t *out_32 = (global uint32_t *)index_buffer;
global uint16_t *out_16 = (global uint16_t *)index_buffer;
global uint8_t *out_8 = (global uint8_t *)index_buffer;
if (index_size_B == 4)
out_32[id] = value;
else if (index_size_B == 2)
out_16[id] = value;
else
out_8[id] = value;
}
static uint
load_index(constant uint8_t *index_buffer, uint32_t index_buffer_range_el,
uint id, uint index_size)
{
/* We have no index buffer, index is the id */
if (index_buffer == 0u)
return id;
/* When no index_buffer is present, index_buffer_range_el is vtx count */
bool oob = id >= index_buffer_range_el;
/* If the load would be out-of-bounds, load the first element which is
* assumed valid. If the application index buffer is empty with robustness2,
* index_buffer will point to a zero sink where only the first is valid.
*/
if (oob) {
id = 0u;
}
uint el;
if (index_size == 1) {
el = ((constant uint8_t *)index_buffer)[id];
} else if (index_size == 2) {
el = ((constant uint16_t *)index_buffer)[id];
} else {
el = ((constant uint32_t *)index_buffer)[id];
}
/* D3D robustness semantics. TODO: Optimize? */
if (oob) {
el = 0;
}
return el;
}
/*
* Return the ID of the first thread in the workgroup where cond is true, or
* 1024 if cond is false across the workgroup.
*/
static uint
first_true_thread_in_workgroup(bool cond, local uint *scratch)
{
barrier(CLK_LOCAL_MEM_FENCE);
scratch[get_sub_group_id()] = sub_group_ballot(cond)[0];
barrier(CLK_LOCAL_MEM_FENCE);
uint first_group =
ctz(sub_group_ballot(scratch[get_sub_group_local_id()])[0]);
uint off = ctz(first_group < 32 ? scratch[first_group] : 0);
return (first_group * 32) + off;
}
// TODO_KOSMICKRISP
// KERNEL(1024)
void
libkk_unroll_geometry_and_restart(
constant uint8_t *index_buffer, global uint8_t *out_ptr,
constant uint32_t *in_draw, global uint32_t *out_draw,
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 tid = cl_local_id.x;
uint count = in_draw[0];
constant uint8_t *in_ptr =
index_buffer ? index_buffer + (in_draw[2] * in_el_size_B) : index_buffer;
// local uint scratch[32];
uint out_prims = 0;
uint needle = 0;
uint per_prim = mesa_vertices_per_prim(mode);
while (needle < count) {
/* Search for next restart or the end. Lanes load in parallel. */
uint next_restart = needle;
for (;;) {
uint idx = next_restart + tid;
bool restart =
idx >= count || load_index(in_ptr, index_buffer_size_el, idx,
in_el_size_B) == restart_index;
// uint next_offs = first_true_thread_in_workgroup(restart, scratch);
// next_restart += next_offs;
// if (next_offs < 1024)
// break;
if (restart)
break;
next_restart++;
}
/* Emit up to the next restart. Lanes output in parallel */
uint subcount = next_restart - needle;
uint subprims = u_decomposed_prims_for_vertices(mode, subcount);
uint out_prims_base = out_prims;
for (uint i = tid; i < subprims; /*i += 1024*/ ++i) {
for (uint vtx = 0; vtx < per_prim; ++vtx) {
uint id =
vertex_id_for_topology(mode, flatshade_first, i, vtx, subprims);
uint offset = needle + id;
uint x = ((out_prims_base + i) * per_prim) + vtx;
uint y =
load_index(in_ptr, index_buffer_size_el, offset, in_el_size_B);
store_index(out_ptr, out_el_size_B, x, y);
}
}
out_prims += subprims;
needle = next_restart + 1;
}
if (tid == 0) {
out_draw[0] = out_prims * per_prim; /* indexCount */
out_draw[1] = in_draw[1]; /* instanceCount */
out_draw[2] = 0u; /* firstIndex */
out_draw[3] = index_buffer ? in_draw[3] : in_draw[2]; /* vertexOffset */
out_draw[4] = index_buffer ? in_draw[4] : in_draw[3]; /* firstInstance */
}
}

View file

@ -0,0 +1,70 @@
/*
* 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);
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);
*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)
{
mtl_release(bo->map);
mtl_release(bo->mtl_handle);
FREE(bo);
}

View file

@ -0,0 +1,32 @@
/*
* 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
};
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 */

View file

@ -0,0 +1,209 @@
/*
* 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_buffer *buffer;
if (pCreateInfo->size > KK_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;
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);
buffer->mtl_handle = mtl_new_buffer_with_length(
mem->bo->mtl_handle, buffer->vk.size, info->memoryOffset);
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;
}

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

View file

@ -0,0 +1,124 @@
/*
* 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;
/* 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,
.optimized_layout = false,
.usage = usage,
.format = {.pipe = p_format, .mtl = supported_format->mtl_pixel_format},
.swizzle =
{
.red = supported_format->swizzle.red,
.green = supported_format->swizzle.green,
.blue = supported_format->swizzle.blue,
.alpha = supported_format->swizzle.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);
}

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

View file

@ -0,0 +1,533 @@
/*
* 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;
desc->sets[i] = NULL; /* We also need to set sets to NULL so state doesn't
propagate if we reset it */
desc->sets_not_resident = 0u;
}
}
void
kk_cmd_release_resources(struct kk_device *dev, struct kk_cmd_buffer *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);
/* 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);
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;
}
util_dynarray_init(&cmd->large_bos, NULL);
cmd->vk.dynamic_graphics_state.vi = &cmd->state.gfx._dynamic_vi;
cmd->vk.dynamic_graphics_state.ms.sample_locations =
&cmd->state.gfx._dynamic_sl;
*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);
}
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. Therefore, we can always look at the current value
* of set_dynamic_buffer_start[s] as the base of our dynamic buffer
* range and it's only our responsibility to adjust all
* set_dynamic_buffer_start[p] for p > s as needed.
*/
uint8_t dyn_buffer_start =
desc->root.set_dynamic_buffer_start[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;
desc->sets_not_resident |= BITFIELD_BIT(s);
/* 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->dynamic_buffer_count > 0) {
for (uint32_t j = 0; j < set_layout->dynamic_buffer_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->dynamic_buffer_count;
}
dyn_buffer_start += set_layout->dynamic_buffer_count;
} else {
assert(set == NULL);
}
}
assert(dyn_buffer_start <= KK_MAX_DYNAMIC_BUFFERS);
assert(next_dyn_offset <= info->dynamicOffsetCount);
for (uint32_t s = info->firstSet + info->descriptorSetCount; s < KK_MAX_SETS;
s++)
desc->root.set_dynamic_buffer_start[s] = dyn_buffer_start;
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)
{
struct kk_device *dev = kk_cmd_buffer_device(cmd);
assert(set < KK_MAX_SETS);
if (unlikely(desc->push[set] == NULL)) {
size_t size = sizeof(*desc->push[set]) +
(sizeof(mtl_resource *) * set_layout->descriptor_count);
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;
}
desc->push[set]->layout = set_layout;
for (uint32_t i = 0u; i < set_layout->descriptor_count; ++i)
desc->push[set]->mtl_resources[i] = dev->null_descriptor->map;
}
/* Pushing descriptors replaces whatever sets are bound */
desc->sets[set] = NULL;
desc->push_dirty |= BITFIELD_BIT(set);
desc->sets_not_resident |= 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_bo *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;
}
struct kk_bo *
kk_cmd_allocate_buffer(struct kk_cmd_buffer *cmd, size_t size_B,
size_t alignment_B)
{
struct kk_bo *buffer = NULL;
VkResult result = kk_alloc_bo(kk_cmd_buffer_device(cmd), &cmd->vk.base,
size_B, alignment_B, &buffer);
if (result != VK_SUCCESS) {
vk_command_buffer_set_error(&cmd->vk, result);
return NULL;
}
util_dynarray_append(&cmd->large_bos, struct kk_bo *, buffer);
return buffer;
}
struct kk_pool
kk_pool_upload(struct kk_cmd_buffer *cmd, void *data, size_t size_B,
size_t alignment_B)
{
struct kk_bo *bo = kk_cmd_allocate_buffer(cmd, size_B, alignment_B);
if (!bo)
return (struct kk_pool){};
memcpy(bo->cpu, data, size_B);
struct kk_pool pool = {.handle = bo->map, .gpu = bo->gpu, .cpu = bo->cpu};
return pool;
}
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_bo *bo = kk_cmd_allocate_buffer(cmd, sizeof(*root), 8u);
if (bo == NULL)
return 0u;
memcpy(bo->cpu, root, sizeof(*root));
root->root_buffer = bo;
return bo->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_bo *bo = kk_cmd_allocate_buffer(cmd, sizeof(push_set->data),
KK_MIN_UBO_ALIGNMENT);
if (bo == NULL)
return;
memcpy(bo->cpu, push_set->data, sizeof(push_set->data));
push_set->mtl_descriptor_buffer = bo->map;
desc->root.sets[set_idx] = bo->gpu;
desc->set_sizes[set_idx] = sizeof(push_set->data);
}
desc->root_dirty = true;
desc->push_dirty = 0;
}
static void
kk_make_graphics_descriptor_resources_resident(struct kk_cmd_buffer *cmd)
{
struct kk_descriptor_state *desc = &cmd->state.gfx.descriptors;
mtl_render_encoder *encoder = kk_render_encoder(cmd);
/* Make resources resident as required by Metal */
u_foreach_bit(set_index, desc->sets_not_resident) {
mtl_resource *descriptor_buffer = NULL;
/* If we have no set, it means it was a push set */
if (desc->sets[set_index]) {
struct kk_descriptor_set *set = desc->sets[set_index];
descriptor_buffer = set->mtl_descriptor_buffer;
} else {
struct kk_push_descriptor_set *push_set = desc->push[set_index];
descriptor_buffer = push_set->mtl_descriptor_buffer;
}
/* We could have empty descriptor sets for some reason... */
if (descriptor_buffer) {
mtl_render_use_resource(encoder, descriptor_buffer,
MTL_RESOURCE_USAGE_READ);
}
}
desc->sets_not_resident = 0u;
}
static void
kk_make_compute_descriptor_resources_resident(struct kk_cmd_buffer *cmd)
{
struct kk_descriptor_state *desc = &cmd->state.cs.descriptors;
mtl_compute_encoder *encoder = kk_compute_encoder(cmd);
u_foreach_bit(set_index, desc->sets_not_resident) {
/* Make resources resident as required by Metal */
mtl_resource *descriptor_buffer = NULL;
if (desc->sets[set_index]) {
struct kk_descriptor_set *set = desc->sets[set_index];
descriptor_buffer = set->mtl_descriptor_buffer;
} else {
struct kk_push_descriptor_set *push_set = desc->push[set_index];
descriptor_buffer = push_set->mtl_descriptor_buffer;
}
/* We could have empty descriptor sets for some reason... */
if (descriptor_buffer) {
mtl_compute_use_resource(encoder, descriptor_buffer,
MTL_RESOURCE_USAGE_READ);
}
}
desc->sets_not_resident = 0u;
}
void
kk_make_descriptor_resources_resident(struct kk_cmd_buffer *cmd,
VkPipelineBindPoint bind_point)
{
if (bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS)
kk_make_graphics_descriptor_resources_resident(cmd);
else if (bind_point == VK_PIPELINE_BIND_POINT_COMPUTE)
kk_make_compute_descriptor_resources_resident(cmd);
}
void
kk_cmd_write(struct kk_cmd_buffer *cmd, mtl_buffer *buffer, uint64_t addr,
uint64_t value)
{
util_dynarray_append(&cmd->encoder->imm_writes, uint64_t, addr);
util_dynarray_append(&cmd->encoder->imm_writes, uint64_t, value);
util_dynarray_append(&cmd->encoder->resident_buffers, mtl_buffer *, buffer);
}
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);
}

View file

@ -0,0 +1,270 @@
/*
* 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 "util/u_dynarray.h"
#include "vk_command_buffer.h"
#include <stdio.h>
struct kk_query_pool;
struct kk_root_descriptor_table {
struct kk_bo *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];
float blend_constant[4];
} 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];
mtl_resource **resources[KK_MAX_SETS];
/* Non resident sets can either be sets or push. If sets[index] == NULL, then
* push[index] != NULL */
uint32_t sets_not_resident;
uint32_t push_dirty;
struct kk_push_descriptor_set *push[KK_MAX_SETS];
};
struct kk_attachment {
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_INDEX = BITFIELD_BIT(0),
KK_DIRTY_VB = BITFIELD_BIT(1),
KK_DIRTY_OCCLUSION = BITFIELD_BIT(2),
KK_DIRTY_PROVOKING = BITFIELD_BIT(3),
KK_DIRTY_VARYINGS = BITFIELD_BIT(4),
KK_DIRTY_PIPELINE = BITFIELD_BIT(5),
};
struct kk_graphics_state {
struct kk_rendering_state render;
struct kk_descriptor_state descriptors;
mtl_render_pipeline_state *pipeline_state;
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 restart_disabled;
enum mtl_primitive_type primitive_type;
enum mesa_prim prim;
enum kk_dirty dirty;
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;
uint32_t size;
uint32_t offset;
uint32_t restart;
uint8_t bytes_per_index;
} index;
/* Vertex buffers */
struct {
struct kk_addr_range addr_range[KK_MAX_VBUFS];
mtl_buffer *handles[KK_MAX_VBUFS];
uint32_t attribs_read;
/* Required to understand maximum size of index buffer if primitive is
* triangle fans */
uint32_t max_vertices;
} vb;
/* 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;
mtl_compute_pipeline_state *pipeline_state;
struct mtl_size local_size;
enum kk_dirty dirty;
};
struct kk_encoder;
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;
} state;
/* Owned large BOs */
struct util_dynarray large_bos;
};
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.gfx.dirty = ~0u;
}
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);
/* Allocates temporary buffer that will be released once the command buffer has
* completed */
struct kk_bo *kk_cmd_allocate_buffer(struct kk_cmd_buffer *cmd, size_t size_B,
size_t alignment_B);
struct kk_pool {
mtl_buffer *handle;
uint64_t gpu;
void *cpu;
};
struct kk_pool kk_pool_upload(struct kk_cmd_buffer *cmd, void *data,
size_t size_B, size_t alignment_B);
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);
void kk_make_descriptor_resources_resident(struct kk_cmd_buffer *cmd,
VkPipelineBindPoint bind_point);
void kk_cmd_write(struct kk_cmd_buffer *cmd, mtl_buffer *buffer, uint64_t addr,
uint64_t value);
void kk_cmd_dispatch_pipeline(struct kk_cmd_buffer *cmd,
mtl_compute_encoder *encoder,
mtl_compute_pipeline_state *pipeline,
const void *push_data, size_t push_size,
uint32_t groupCountX, uint32_t groupCountY,
uint32_t groupCountZ);
#endif

View file

@ -0,0 +1,169 @@
/*
* 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);
kk_CmdEndRendering(kk_cmd_buffer_to_handle(cmd));
}
}
}
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);
}

View file

@ -0,0 +1,355 @@
/*
* 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;
}
#define kk_foreach_slice(ndx, image, subresource_member) \
for (uint32_t ndx = region->subresource_member.baseArrayLayer; \
ndx < (region->subresource_member.baseArrayLayer + \
vk_image_subresource_layer_count(&image->vk, \
&region->subresource_member)); \
++ndx)
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;
}
}
}
struct copy_image_data {
struct kk_cmd_buffer *cmd;
struct kk_image *src;
struct kk_image *dst;
const VkImageCopy2 *regions;
uint32_t plane_index;
uint32_t region_count;
};
/* Copies images by doing a texture->buffer->texture transfer. This is required
* for compressed formats */
static void
copy_through_buffer(struct copy_image_data *data)
{
struct kk_image *src = data->src;
struct kk_image *dst = data->dst;
struct kk_image_plane *src_plane = &src->planes[data->plane_index];
struct kk_image_plane *dst_plane = &dst->planes[data->plane_index];
enum pipe_format src_format = src_plane->layout.format.pipe;
enum pipe_format dst_format = dst_plane->layout.format.pipe;
bool is_src_compressed = util_format_is_compressed(src_format);
bool is_dst_compressed = util_format_is_compressed(dst_format);
/* We shouldn't do any depth/stencil through this path */
assert(!util_format_is_depth_or_stencil(src_format) ||
!util_format_is_depth_or_stencil(dst_format));
mtl_blit_encoder *blit = kk_blit_encoder(data->cmd);
size_t buffer_size = 0u;
for (unsigned r = 0; r < data->region_count; r++) {
const VkImageCopy2 *region = &data->regions[r];
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;
}
struct kk_bo *bo = kk_cmd_allocate_buffer(data->cmd, buffer_size, 8);
size_t buffer_offset = 0u;
for (unsigned r = 0; r < data->region_count; r++) {
const VkImageCopy2 *region = &data->regions[r];
uint32_t mip_level = region->srcSubresource.mipLevel;
const uint32_t mip_width =
u_minify(src_plane->layout.width_px, mip_level);
const uint32_t mip_height =
u_minify(src_plane->layout.height_px, mip_level);
const uint32_t stride_B = util_format_get_stride(src_format, mip_width);
const uint32_t size_2d_B =
util_format_get_2d_size(src_format, stride_B, mip_height);
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 : size_2d_B;
info.mtl_data.buffer_stride_B = buffer_stride_B;
info.mtl_data.image_level = mip_level;
info.mtl_data.buffer = bo->map;
info.mtl_data.options = MTL_BLIT_OPTION_NONE;
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_blockwidth(src_format);
dst_size.y /= util_format_get_blockheight(src_format);
dst_size.z /= util_format_get_blockdepth(src_format);
}
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);
}
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?
kk_foreach_slice(slice, src, srcSubresource)
{
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 = slice;
info.mtl_data.buffer_offset_B = buffer_offset;
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;
mtl_copy_from_buffer_to_texture(blit, &info.mtl_data);
buffer_offset += info.buffer_slice_size_B;
}
}
}
/* Copies images through Metal's texture->texture copy mechanism */
static void
copy_image(struct copy_image_data *data)
{
mtl_blit_encoder *blit = kk_blit_encoder(data->cmd);
for (unsigned r = 0; r < data->region_count; r++) {
const VkImageCopy2 *region = &data->regions[r];
uint8_t src_plane_index = kk_image_aspects_to_plane(
data->src, region->srcSubresource.aspectMask);
if (data->plane_index != src_plane_index)
continue;
uint8_t dst_plane_index = kk_image_aspects_to_plane(
data->dst, region->dstSubresource.aspectMask);
struct kk_image *src = data->src;
struct kk_image *dst = data->dst;
struct kk_image_plane *src_plane = &src->planes[src_plane_index];
struct kk_image_plane *dst_plane = &dst->planes[dst_plane_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);
for (uint32_t i = 0u; i < src->plane_count; ++i) {
struct kk_image_plane *src_plane = &src->planes[i];
struct kk_image_plane *dst_plane = &dst->planes[i];
enum pipe_format src_format = src_plane->layout.format.pipe;
enum pipe_format dst_format = dst_plane->layout.format.pipe;
struct copy_image_data data = {
.cmd = cmd,
.src = src,
.dst = dst,
.regions = pCopyImageInfo->pRegions,
.plane_index = i,
.region_count = pCopyImageInfo->regionCount,
};
bool is_src_compressed = util_format_is_compressed(src_format);
bool is_dst_compressed = util_format_is_compressed(dst_format);
if (src_format != dst_format && (is_src_compressed || is_dst_compressed))
copy_through_buffer(&data);
else
copy_image(&data);
}
}

View file

@ -0,0 +1,152 @@
/*
* 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"
void
kk_cmd_dispatch_pipeline(struct kk_cmd_buffer *cmd,
mtl_compute_encoder *encoder,
mtl_compute_pipeline_state *pipeline,
const void *push_data, size_t push_size,
uint32_t groupCountX, uint32_t groupCountY,
uint32_t groupCountZ)
{
struct kk_root_descriptor_table *root = NULL;
struct kk_bo *bo = kk_cmd_allocate_buffer(cmd, sizeof(*root), 8u);
/* kk_cmd_allocate_buffer already sets the error, we can just exit */
if (!bo)
return;
root = bo->cpu;
assert(push_size <= sizeof(root->push));
memcpy(root->push, push_data, push_size);
root->cs.base_group[0] = 1; /* TODO_KOSMICKRISP This is hard-coded because we
know this is the size we create them with */
root->cs.base_group[1] = 1;
root->cs.base_group[2] = 1;
mtl_compute_set_buffer(encoder, bo->map, 0, 0);
mtl_compute_set_pipeline_state(encoder, pipeline);
struct mtl_size grid_size = {
.x = groupCountX,
.y = groupCountY,
.z = groupCountZ,
};
struct mtl_size local_size = {
.x = 1,
.y = 1,
.z = 1,
};
mtl_dispatch_threads(encoder, grid_size, local_size);
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdDispatch(VkCommandBuffer commandBuffer, uint32_t groupCountX,
uint32_t groupCountY, uint32_t groupCountZ)
{
kk_CmdDispatchBase(commandBuffer, 0, 0, 0, groupCountX, groupCountY,
groupCountZ);
}
static void
kk_flush_compute_state(struct kk_cmd_buffer *cmd)
{
mtl_compute_encoder *enc = kk_compute_encoder(cmd);
struct kk_device *dev = kk_cmd_buffer_device(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);
/* After push descriptors' buffers are created. Otherwise, the buffer where
* they live will not be created and cannot make it resident */
if (desc->sets_not_resident)
kk_make_descriptor_resources_resident(cmd,
VK_PIPELINE_BIND_POINT_COMPUTE);
if (desc->root_dirty)
kk_upload_descriptor_root(cmd, VK_PIPELINE_BIND_POINT_COMPUTE);
/* Make user allocated heaps resident */
simple_mtx_lock(&dev->user_heap_cache.mutex);
if (cmd->encoder->main.user_heap_hash != dev->user_heap_cache.hash) {
cmd->encoder->main.user_heap_hash = dev->user_heap_cache.hash;
mtl_heap **heaps = util_dynarray_begin(&dev->user_heap_cache.handles);
uint32_t count =
util_dynarray_num_elements(&dev->user_heap_cache.handles, mtl_heap *);
mtl_compute_use_heaps(enc, heaps, count);
}
simple_mtx_unlock(&dev->user_heap_cache.mutex);
struct kk_bo *root_buffer = desc->root.root_buffer;
if (root_buffer)
mtl_compute_set_buffer(enc, root_buffer->map, 0, 0);
mtl_compute_set_pipeline_state(enc, cmd->state.cs.pipeline_state);
cmd->state.cs.dirty = 0u;
}
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)
{
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;
kk_flush_compute_state(cmd);
struct mtl_size grid_size = {
.x = groupCountX,
.y = groupCountY,
.z = groupCountZ,
};
mtl_compute_encoder *enc = kk_compute_encoder(cmd);
mtl_dispatch_threads(enc, grid_size, cmd->state.cs.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;
kk_flush_compute_state(cmd);
mtl_compute_encoder *enc = kk_compute_encoder(cmd);
mtl_dispatch_threadgroups_with_indirect_buffer(
enc, buffer->mtl_handle, offset, cmd->state.cs.local_size);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,318 @@
/*
* 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_bo *bo = kk_cmd_allocate_buffer(cmd, buffer->vk.size, 16u);
if (unlikely(bo == NULL))
return VK_ERROR_OUT_OF_POOL_MEMORY;
/* Need to retain since VkBuffers release the mtl_handle too */
mtl_retain(bo->map);
buffer->mtl_handle = bo->map;
buffer->vk.device_address = bo->gpu;
*map_out = bo->cpu;
mtl_compute_use_resource(cmd->encoder->main.encoder, buffer->mtl_handle,
MTL_RESOURCE_USAGE_WRITE | MTL_RESOURCE_USAGE_READ);
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 {
union {
struct {
mtl_render_pipeline_state *ps;
mtl_depth_stencil_state *ds;
uint32_t attribs_read;
enum mtl_primitive_type primitive_type;
enum mtl_visibility_result_mode occlusion;
bool is_ds_dynamic;
} gfx;
struct {
mtl_compute_pipeline_state *pipeline_state;
struct mtl_size local_size;
} cs;
};
} pipeline;
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;
save->pipeline.gfx.ps = cmd->state.gfx.pipeline_state;
save->pipeline.gfx.ds = cmd->state.gfx.depth_stencil_state;
save->pipeline.gfx.attribs_read = cmd->state.gfx.vb.attribs_read;
save->pipeline.gfx.primitive_type = cmd->state.gfx.primitive_type;
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->pipeline.cs.pipeline_state = cmd->state.cs.pipeline_state;
save->pipeline.cs.local_size = cmd->state.cs.local_size;
}
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->sets_not_resident |= BITFIELD_BIT(0);
desc->push_dirty &= ~BITFIELD_BIT(0);
} else if (save->has_push_desc0) {
desc->push[0] = save->push_desc0;
desc->sets_not_resident |= BITFIELD_BIT(0);
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));
if (cmd->state.gfx.is_depth_stencil_dynamic)
mtl_release(cmd->state.gfx.depth_stencil_state);
cmd->state.gfx.pipeline_state = save->pipeline.gfx.ps;
cmd->state.gfx.depth_stencil_state = save->pipeline.gfx.ds;
cmd->state.gfx.primitive_type = save->pipeline.gfx.primitive_type;
cmd->state.gfx.vb.attribs_read = save->pipeline.gfx.attribs_read;
cmd->state.gfx.is_depth_stencil_dynamic =
save->pipeline.gfx.is_ds_dynamic;
cmd->state.gfx.dirty |= KK_DIRTY_PIPELINE;
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 {
cmd->state.cs.local_size = save->pipeline.cs.local_size;
cmd->state.cs.pipeline_state = save->pipeline.cs.pipeline_state;
}
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);
VK_FROM_HANDLE(kk_buffer, buf, dstBuffer);
struct kk_device *dev = kk_cmd_buffer_device(cmd);
struct kk_meta_save save;
kk_meta_begin(cmd, &save, VK_PIPELINE_BIND_POINT_COMPUTE);
mtl_compute_use_resource(kk_compute_encoder(cmd), buf->mtl_handle,
MTL_RESOURCE_USAGE_WRITE);
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);
VK_FROM_HANDLE(kk_buffer, buf, dstBuffer);
struct kk_device *dev = kk_cmd_buffer_device(cmd);
struct kk_meta_save save;
kk_meta_begin(cmd, &save, VK_PIPELINE_BIND_POINT_COMPUTE);
mtl_compute_use_resource(kk_compute_encoder(cmd), buf->mtl_handle,
MTL_RESOURCE_USAGE_WRITE);
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);
struct kk_meta_save save;
kk_meta_begin(cmd, &save, VK_PIPELINE_BIND_POINT_GRAPHICS);
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);
}

View file

@ -0,0 +1,64 @@
/*
* 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"
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_mem);
list_inithead(&pool->free_gart_mem);
*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);
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);
}

View file

@ -0,0 +1,32 @@
/*
* 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"
struct kk_cmd_pool {
struct vk_command_pool vk;
/** List of nvk_cmd_mem */
struct list_head free_mem;
struct list_head free_gart_mem;
};
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;
}
#endif /* KK_CMD_POOL_H */

View file

@ -0,0 +1,22 @@
/*
* 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},
{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();
}

View file

@ -0,0 +1,21 @@
/*
* 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,
};
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 */

View file

@ -0,0 +1,806 @@
/*
* 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->dynamic_buffer_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;
}
set->mtl_descriptor_buffer = pool->bo->map;
}
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);
}

View file

@ -0,0 +1,81 @@
/*
* 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;
mtl_resource *mtl_descriptor_buffer;
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;
mtl_resource *mtl_descriptor_buffer;
uint32_t resource_count;
mtl_resource *mtl_resources[];
};
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

View file

@ -0,0 +1,496 @@
/*
* 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)
*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;
uint32_t total_descriptor_count = 0u;
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->binding[b].mtl_resources_index = total_descriptor_count;
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_RANGE(layout->dynamic_ubos, dynamic_buffer_count,
dynamic_buffer_count + binding->descriptorCount - 1);
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;
layout->binding[b].count_per_element = max_plane_count;
total_descriptor_count += max_plane_count * binding->descriptorCount;
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->dynamic_buffer_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->dynamic_buffer_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;
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;
} 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;
if (variable_stride > 0) {
vs->maxVariableDescriptorCount =
(max_buffer_size - non_variable_size) / variable_stride;
} else {
vs->maxVariableDescriptorCount = 0;
}
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;
}

View file

@ -0,0 +1,103 @@
/*
* 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;
/* Number of actual descriptors per element */
uint32_t count_per_element;
/* Offset into the descriptor buffer where this descriptor lives */
uint32_t offset;
/* Offset to the mtl_resource_ids array where this descriptor stores them */
uint32_t mtl_resources_index;
/* 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;
/* Number of dynamic UBO bindings in this set */
uint8_t dynamic_buffer_count;
/* 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 */

View file

@ -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 */

View file

@ -0,0 +1,348 @@
/*
* 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);
}
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;
/* Buffer to use as null descriptor */
result = kk_alloc_bo(dev, &dev->vk.base, sizeof(uint64_t) * 8, 8u,
&dev->null_descriptor);
if (result != VK_SUCCESS)
goto fail_init;
result =
kk_queue_init(dev, &dev->queue, &pCreateInfo->pQueueCreateInfos[0], 0);
if (result != VK_SUCCESS)
goto fail_vab_memory;
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;
simple_mtx_init(&dev->user_heap_cache.mutex, mtx_plain);
util_dynarray_init(&dev->user_heap_cache.handles, NULL);
*pDevice = kk_device_to_handle(dev);
dev->gpu_capture_enabled = kk_get_environment_boolean(KK_ENABLE_GPU_CAPTURE);
mtl_start_gpu_capture(dev->mtl_handle);
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:
kk_queue_finish(dev, &dev->queue);
fail_vab_memory:
kk_destroy_bo(dev, dev->null_descriptor);
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;
/* Meta first since it may destroy Vulkan objects */
kk_device_finish_meta(dev);
util_dynarray_fini(&dev->user_heap_cache.handles);
simple_mtx_destroy(&dev->user_heap_cache.mutex);
kk_device_finish_lib(dev);
kk_query_table_finish(dev, &dev->occlusion_queries);
kk_destroy_sampler_heap(dev, &dev->samplers);
kk_queue_finish(dev, &dev->queue);
kk_destroy_bo(dev, dev->null_descriptor);
vk_device_finish(&dev->vk);
if (dev->gpu_capture_enabled) {
mtl_stop_gpu_capture();
}
vk_free(&dev->vk.alloc, dev);
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_GetCalibratedTimestampsKHR(
VkDevice _device, uint32_t timestampCount,
const VkCalibratedTimestampInfoKHR *pTimestampInfos, uint64_t *pTimestamps,
uint64_t *pMaxDeviation)
{
uint64_t max_clock_period = 0;
uint64_t begin, end;
int d;
#ifdef CLOCK_MONOTONIC_RAW
begin = vk_clock_gettime(CLOCK_MONOTONIC_RAW);
#else
begin = vk_clock_gettime(CLOCK_MONOTONIC);
#endif
for (d = 0; d < timestampCount; d++) {
switch (pTimestampInfos[d].timeDomain) {
case VK_TIME_DOMAIN_CLOCK_MONOTONIC_KHR:
pTimestamps[d] = vk_clock_gettime(CLOCK_MONOTONIC);
max_clock_period = MAX2(max_clock_period, 1);
break;
#ifdef CLOCK_MONOTONIC_RAW
case VK_TIME_DOMAIN_CLOCK_MONOTONIC_RAW_KHR:
pTimestamps[d] = begin;
break;
#endif
default:
pTimestamps[d] = 0;
break;
}
}
#ifdef CLOCK_MONOTONIC_RAW
end = vk_clock_gettime(CLOCK_MONOTONIC_RAW);
#else
end = vk_clock_gettime(CLOCK_MONOTONIC);
#endif
*pMaxDeviation = vk_time_max_deviation(begin, end, max_clock_period);
return VK_SUCCESS;
}
/* 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_user_heap(struct kk_device *dev, mtl_heap *heap)
{
simple_mtx_lock(&dev->user_heap_cache.mutex);
util_dynarray_append(&dev->user_heap_cache.handles, mtl_heap *, heap);
dev->user_heap_cache.hash += 1u;
simple_mtx_unlock(&dev->user_heap_cache.mutex);
}
void
kk_device_remove_user_heap(struct kk_device *dev, mtl_heap *heap)
{
simple_mtx_lock(&dev->user_heap_cache.mutex);
util_dynarray_delete_unordered(&dev->user_heap_cache.handles, mtl_heap *,
heap);
simple_mtx_unlock(&dev->user_heap_cache.mutex);
}

View file

@ -0,0 +1,137 @@
/*
* 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 "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;
enum kk_device_lib_pipeline {
KK_LIB_IMM_WRITE = 0,
KK_LIB_COPY_QUERY,
KK_LIB_TRIANGLE_FAN,
KK_LIB_COUNT,
};
struct kk_user_heap_cache {
simple_mtx_t mutex;
uint32_t hash;
struct util_dynarray handles;
};
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 agx_sampler_packed to hk_rc_sampler */
struct hash_table *ht;
};
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_bo *null_descriptor;
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_user_heap_cache user_heap_cache;
mtl_compute_pipeline_state *lib_pipelines[KK_LIB_COUNT];
struct kk_queue queue;
struct vk_meta_device meta;
bool gpu_capture_enabled;
};
VK_DEFINE_HANDLE_CASTS(kk_device, vk.base, VkDevice, VK_OBJECT_TYPE_DEVICE)
static inline mtl_compute_pipeline_state *
kk_device_lib_pipeline(const struct kk_device *dev,
enum kk_device_lib_pipeline pipeline)
{
assert(pipeline < KK_LIB_COUNT);
return dev->lib_pipelines[pipeline];
}
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_user_heap(struct kk_device *dev, mtl_heap *heap);
void kk_device_remove_user_heap(struct kk_device *dev, mtl_heap *heap);
/* 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

View file

@ -0,0 +1,191 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_device.h"
#include "kk_shader.h"
#include "kkcl.h"
#include "kosmickrisp/bridge/mtl_bridge.h"
#include "nir/nir.h"
#include "nir/nir_builder.h"
static nir_def *
load_struct_var(nir_builder *b, nir_variable *var, uint32_t field)
{
nir_deref_instr *deref =
nir_build_deref_struct(b, nir_build_deref_var(b, var), field);
return nir_load_deref(b, deref);
}
static nir_shader *
create_imm_write_shader()
{
nir_builder build = nir_builder_init_simple_shader(MESA_SHADER_COMPUTE, NULL,
"kk-meta-imm-write-u64");
nir_builder *b = &build;
struct glsl_struct_field push_fields[] = {
{.type = glsl_uint64_t_type(), .name = "buffer_address", .offset = 0},
};
const struct glsl_type *push_iface_type = glsl_interface_type(
push_fields, ARRAY_SIZE(push_fields), GLSL_INTERFACE_PACKING_STD140,
false /* row_major */, "push");
nir_variable *push = nir_variable_create(b->shader, nir_var_mem_push_const,
push_iface_type, "push");
b->shader->info.workgroup_size[0] = 1;
b->shader->info.workgroup_size[1] = 1;
b->shader->info.workgroup_size[2] = 1;
libkk_write_u64(b, load_struct_var(b, push, 0));
return build.shader;
}
static nir_shader *
create_copy_query_shader()
{
nir_builder build = nir_builder_init_simple_shader(MESA_SHADER_COMPUTE, NULL,
"kk-meta-copy-queries");
nir_builder *b = &build;
struct glsl_struct_field push_fields[] = {
{.type = glsl_uint64_t_type(), .name = "availability", .offset = 0},
{.type = glsl_uint64_t_type(), .name = "results", .offset = 8},
{.type = glsl_uint64_t_type(), .name = "indices", .offset = 16},
{.type = glsl_uint64_t_type(), .name = "dst_addr", .offset = 24},
{.type = glsl_uint64_t_type(), .name = "dst_stride", .offset = 32},
{.type = glsl_uint_type(), .name = "first_query", .offset = 40},
{.type = glsl_uint_type(), .name = "flags", .offset = 44},
{.type = glsl_uint16_t_type(), .name = "reports_per_query", .offset = 48},
};
/* TODO_KOSMICKRISP Don't use push constants and directly bind the buffer to
* the binding index. This requires compiler work first to remove the
* hard-coded buffer0 value. Same applies to other creation functions.
*/
const struct glsl_type *push_iface_type = glsl_interface_type(
push_fields, ARRAY_SIZE(push_fields), GLSL_INTERFACE_PACKING_STD140,
false /* row_major */, "push");
nir_variable *push = nir_variable_create(b->shader, nir_var_mem_push_const,
push_iface_type, "push");
b->shader->info.workgroup_size[0] = 1;
b->shader->info.workgroup_size[1] = 1;
b->shader->info.workgroup_size[2] = 1;
libkk_copy_queries(b, load_struct_var(b, push, 0),
load_struct_var(b, push, 1), load_struct_var(b, push, 2),
load_struct_var(b, push, 3), load_struct_var(b, push, 4),
load_struct_var(b, push, 5), load_struct_var(b, push, 6),
load_struct_var(b, push, 7));
return build.shader;
}
static nir_shader *
create_triangle_fan_shader()
{
nir_builder build = nir_builder_init_simple_shader(
MESA_SHADER_COMPUTE, NULL, "kk-device-unroll-geomtry-and-restart");
nir_builder *b = &build;
struct glsl_struct_field push_fields[] = {
{.type = glsl_uint64_t_type(), .name = "index_buffer", .offset = 0},
{.type = glsl_uint64_t_type(), .name = "out_ptr", .offset = 8},
{.type = glsl_uint64_t_type(), .name = "indirect_in", .offset = 16},
{.type = glsl_uint64_t_type(), .name = "indirect_out", .offset = 24},
{.type = glsl_uint_type(), .name = "restart_index", .offset = 32},
{.type = glsl_uint_type(), .name = "index_buffer_size_el", .offset = 36},
{.type = glsl_uint_type(), .name = "in_el_size_B,", .offset = 40},
{.type = glsl_uint_type(), .name = "out_el_size_B,", .offset = 44},
{.type = glsl_uint_type(), .name = "flatshade_first", .offset = 48},
{.type = glsl_uint_type(), .name = "mode", .offset = 52},
};
const struct glsl_type *push_iface_type = glsl_interface_type(
push_fields, ARRAY_SIZE(push_fields), GLSL_INTERFACE_PACKING_STD140,
false /* row_major */, "push");
nir_variable *push = nir_variable_create(b->shader, nir_var_mem_push_const,
push_iface_type, "push");
b->shader->info.workgroup_size[0] = 1;
b->shader->info.workgroup_size[1] = 1;
b->shader->info.workgroup_size[2] = 1;
libkk_unroll_geometry_and_restart(
b, load_struct_var(b, push, 0), load_struct_var(b, push, 1),
load_struct_var(b, push, 2), load_struct_var(b, push, 3),
load_struct_var(b, push, 4), load_struct_var(b, push, 5),
load_struct_var(b, push, 6), load_struct_var(b, push, 7),
load_struct_var(b, push, 8), load_struct_var(b, push, 9));
return build.shader;
}
static struct {
enum kk_device_lib_pipeline ndx;
nir_shader *(*create_shader_fn)();
} lib_shaders[KK_LIB_COUNT] = {
{KK_LIB_IMM_WRITE, create_imm_write_shader},
{KK_LIB_COPY_QUERY, create_copy_query_shader},
{KK_LIB_TRIANGLE_FAN, create_triangle_fan_shader},
};
static_assert(ARRAY_SIZE(lib_shaders) == KK_LIB_COUNT,
"Device lib shader count and created shader count mismatch");
VkResult
kk_device_init_lib(struct kk_device *dev)
{
VkResult result = VK_SUCCESS;
uint32_t i = 0u;
for (; i < KK_LIB_COUNT; ++i) {
nir_shader *s = lib_shaders[i].create_shader_fn();
if (!s)
goto fail;
struct kk_shader *shader = NULL;
result = kk_compile_nir_shader(dev, s, &dev->vk.alloc, &shader);
if (result != VK_SUCCESS)
goto fail;
mtl_library *library = mtl_new_library(dev->mtl_handle, shader->msl_code);
if (library == NULL)
goto fail;
uint32_t local_size_threads = shader->info.cs.local_size.x *
shader->info.cs.local_size.y *
shader->info.cs.local_size.z;
mtl_function *function =
mtl_new_function_with_name(library, shader->entrypoint_name);
dev->lib_pipelines[i] = mtl_new_compute_pipeline_state(
dev->mtl_handle, function, local_size_threads);
mtl_release(function);
mtl_release(library);
/* We no longer need the shader. Although it may be useful to keep it
* alive for the info maybe? */
shader->vk.ops->destroy(&dev->vk, &shader->vk, &dev->vk.alloc);
if (!dev->lib_pipelines[i])
goto fail;
}
return result;
fail:
for (uint32_t j = 0u; j < i; ++j)
mtl_release(dev->lib_pipelines[j]);
return vk_error(dev, result);
}
void
kk_device_finish_lib(struct kk_device *dev)
{
for (uint32_t i = 0; i < KK_LIB_COUNT; ++i)
mtl_release(dev->lib_pipelines[i]);
}

View file

@ -0,0 +1,258 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_device_memory.h"
#include "kk_device.h"
#include "kk_entrypoints.h"
#include "kk_physical_device.h"
#include "kosmickrisp/bridge/mtl_bridge.h"
#include "vulkan/vulkan_metal.h"
#include "util/u_atomic.h"
#include "util/u_memory.h"
#include <inttypes.h>
#include <sys/mman.h>
/* Supports mtlheap only */
const VkExternalMemoryProperties kk_mtlheap_mem_props = {
.externalMemoryFeatures = VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT |
VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT,
.exportFromImportedHandleTypes =
VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLHEAP_BIT_EXT,
.compatibleHandleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLHEAP_BIT_EXT,
};
#ifdef VK_USE_PLATFORM_METAL_EXT
VKAPI_ATTR VkResult VKAPI_CALL
kk_GetMemoryMetalHandlePropertiesEXT(
VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType,
const void *pHandle,
VkMemoryMetalHandlePropertiesEXT *pMemoryMetalHandleProperties)
{
VK_FROM_HANDLE(kk_device, dev, device);
struct kk_physical_device *pdev = kk_device_physical(dev);
/* We only support heaps since that's the backing for all our memory and
* simplifies implementation */
switch (handleType) {
case VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLHEAP_BIT_EXT:
break;
default:
return vk_error(dev, VK_ERROR_INVALID_EXTERNAL_HANDLE);
}
pMemoryMetalHandleProperties->memoryTypeBits =
BITFIELD_MASK(pdev->mem_type_count);
return VK_SUCCESS;
}
#endif /* VK_USE_PLATFORM_METAL_EXT */
VKAPI_ATTR VkResult VKAPI_CALL
kk_AllocateMemory(VkDevice device, const VkMemoryAllocateInfo *pAllocateInfo,
const VkAllocationCallbacks *pAllocator, VkDeviceMemory *pMem)
{
VK_FROM_HANDLE(kk_device, dev, device);
struct kk_physical_device *pdev = kk_device_physical(dev);
struct kk_device_memory *mem;
VkResult result = VK_SUCCESS;
const VkImportMemoryMetalHandleInfoEXT *metal_info = vk_find_struct_const(
pAllocateInfo->pNext, IMPORT_MEMORY_METAL_HANDLE_INFO_EXT);
const VkMemoryType *type = &pdev->mem_types[pAllocateInfo->memoryTypeIndex];
// TODO_KOSMICKRISP Do the actual memory allocation with alignment requirements
uint32_t alignment = (1ULL << 12);
const uint64_t aligned_size =
align64(pAllocateInfo->allocationSize, alignment);
mem = vk_device_memory_create(&dev->vk, pAllocateInfo, pAllocator,
sizeof(*mem));
if (!mem)
return vk_error(dev, VK_ERROR_OUT_OF_HOST_MEMORY);
if (metal_info && metal_info->handleType) {
/* We only support heaps since that's the backing for all our memory and
* simplifies implementation */
assert(metal_info->handleType ==
VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLHEAP_BIT_EXT);
mem->bo = CALLOC_STRUCT(kk_bo);
if (!mem->bo) {
result = vk_errorf(&dev->vk.base, VK_ERROR_OUT_OF_DEVICE_MEMORY, "%m");
goto fail_alloc;
}
mem->bo->mtl_handle = mtl_retain(metal_info->handle);
mem->bo->map =
mtl_new_buffer_with_length(mem->bo->mtl_handle, mem->vk.size, 0u);
mem->bo->gpu = mtl_buffer_get_gpu_address(mem->bo->map);
mem->bo->cpu = mtl_get_contents(mem->bo->map);
mem->bo->size_B = mtl_heap_get_size(mem->bo->mtl_handle);
} else {
result =
kk_alloc_bo(dev, &dev->vk.base, aligned_size, alignment, &mem->bo);
if (result != VK_SUCCESS)
goto fail_alloc;
}
struct kk_memory_heap *heap = &pdev->mem_heaps[type->heapIndex];
p_atomic_add(&heap->used, mem->bo->size_B);
kk_device_add_user_heap(dev, mem->bo->mtl_handle);
*pMem = kk_device_memory_to_handle(mem);
return VK_SUCCESS;
fail_alloc:
vk_device_memory_destroy(&dev->vk, pAllocator, &mem->vk);
return result;
}
VKAPI_ATTR void VKAPI_CALL
kk_FreeMemory(VkDevice device, VkDeviceMemory _mem,
const VkAllocationCallbacks *pAllocator)
{
VK_FROM_HANDLE(kk_device, dev, device);
VK_FROM_HANDLE(kk_device_memory, mem, _mem);
struct kk_physical_device *pdev = kk_device_physical(dev);
if (!mem)
return;
kk_device_remove_user_heap(dev, mem->bo->mtl_handle);
const VkMemoryType *type = &pdev->mem_types[mem->vk.memory_type_index];
struct kk_memory_heap *heap = &pdev->mem_heaps[type->heapIndex];
p_atomic_add(&heap->used, -((int64_t)mem->bo->size_B));
kk_destroy_bo(dev, mem->bo);
vk_device_memory_destroy(&dev->vk, pAllocator, &mem->vk);
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_MapMemory2KHR(VkDevice device, const VkMemoryMapInfoKHR *pMemoryMapInfo,
void **ppData)
{
VK_FROM_HANDLE(kk_device, dev, device);
VK_FROM_HANDLE(kk_device_memory, mem, pMemoryMapInfo->memory);
VkResult result = VK_SUCCESS;
if (mem == NULL) {
*ppData = NULL;
return VK_SUCCESS;
}
const VkDeviceSize offset = pMemoryMapInfo->offset;
const VkDeviceSize size = vk_device_memory_range(
&mem->vk, pMemoryMapInfo->offset, pMemoryMapInfo->size);
/* From the Vulkan spec version 1.0.32 docs for MapMemory:
*
* * If size is not equal to VK_WHOLE_SIZE, size must be greater than 0
* assert(size != 0);
* * If size is not equal to VK_WHOLE_SIZE, size must be less than or
* equal to the size of the memory minus offset
*/
assert(size > 0);
assert(offset + size <= mem->bo->size_B);
if (size != (size_t)size) {
return vk_errorf(dev, VK_ERROR_MEMORY_MAP_FAILED,
"requested size 0x%" PRIx64 " does not fit in %u bits",
size, (unsigned)(sizeof(size_t) * 8));
}
/* From the Vulkan 1.2.194 spec:
*
* "memory must not be currently host mapped"
*/
if (mem->map != NULL) {
return vk_errorf(dev, VK_ERROR_MEMORY_MAP_FAILED,
"Memory object already mapped.");
}
// TODO_KOSMICKRISP Use mmap here to so we can support VK_EXT_map_memory_placed
mem->map = mem->bo->cpu;
*ppData = mem->map + offset;
return result;
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_UnmapMemory2KHR(VkDevice device,
const VkMemoryUnmapInfoKHR *pMemoryUnmapInfo)
{
VK_FROM_HANDLE(kk_device_memory, mem, pMemoryUnmapInfo->memory);
if (mem == NULL)
return VK_SUCCESS;
// TODO_KOSMICKRISP Use unmap here to so we can support
// VK_EXT_map_memory_placed
mem->map = NULL;
return VK_SUCCESS;
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_FlushMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount,
const VkMappedMemoryRange *pMemoryRanges)
{
return VK_SUCCESS;
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_InvalidateMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount,
const VkMappedMemoryRange *pMemoryRanges)
{
return VK_SUCCESS;
}
VKAPI_ATTR void VKAPI_CALL
kk_GetDeviceMemoryCommitment(VkDevice device, VkDeviceMemory _mem,
VkDeviceSize *pCommittedMemoryInBytes)
{
VK_FROM_HANDLE(kk_device_memory, mem, _mem);
*pCommittedMemoryInBytes = mem->bo->size_B;
}
#ifdef VK_USE_PLATFORM_METAL_EXT
VKAPI_ATTR VkResult VKAPI_CALL
kk_GetMemoryMetalHandleEXT(
VkDevice device, const VkMemoryGetMetalHandleInfoEXT *pGetMetalHandleInfo,
void **pHandle)
{
/* We only support heaps since that's the backing for all our memory and
* simplifies implementation */
assert(pGetMetalHandleInfo->handleType ==
VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLHEAP_BIT_EXT);
VK_FROM_HANDLE(kk_device_memory, mem, pGetMetalHandleInfo->memory);
/* From the Vulkan spec of vkGetMemoryMetalHandleEXT:
*
* "Unless the app retains the handle object returned by the call,
* the lifespan will be the same as the associated VkDeviceMemory"
*/
*pHandle = mem->bo->mtl_handle;
return VK_SUCCESS;
}
#endif /* VK_USE_PLATFORM_METAL_EXT */
VKAPI_ATTR uint64_t VKAPI_CALL
kk_GetDeviceMemoryOpaqueCaptureAddress(
UNUSED VkDevice device, const VkDeviceMemoryOpaqueCaptureAddressInfo *pInfo)
{
VK_FROM_HANDLE(kk_device_memory, mem, pInfo->memory);
return mem->bo->gpu;
}

View file

@ -0,0 +1,30 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_MEMORY_H
#define KK_MEMORY_H 1
#include "kk_private.h"
#include "kk_bo.h"
#include "vk_device_memory.h"
#include "util/list.h"
struct kk_device_memory {
struct vk_device_memory vk;
struct kk_bo *bo;
void *map;
};
VK_DEFINE_NONDISP_HANDLE_CASTS(kk_device_memory, vk.base, VkDeviceMemory,
VK_OBJECT_TYPE_DEVICE_MEMORY)
extern const VkExternalMemoryProperties kk_mtlheap_mem_props;
#endif // KK_MEMORY_H

View file

@ -0,0 +1,480 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_encoder.h"
#include "kk_bo.h"
#include "kk_cmd_buffer.h"
#include "kk_queue.h"
#include "kosmickrisp/bridge/mtl_bridge.h"
#include "kosmickrisp/bridge/vk_to_mtl_map.h"
#include "cl/kk_query.h"
static void
kk_encoder_start_internal(struct kk_encoder_internal *encoder,
mtl_device *device, mtl_command_queue *queue)
{
encoder->cmd_buffer = mtl_new_command_buffer(queue);
encoder->last_used = KK_ENC_NONE;
util_dynarray_init(&encoder->fences, NULL);
}
VkResult
kk_encoder_init(mtl_device *device, struct kk_queue *queue,
struct kk_encoder **encoder)
{
assert(encoder && device && queue);
struct kk_encoder *enc = (struct kk_encoder *)malloc(sizeof(*enc));
if (!enc)
return VK_ERROR_OUT_OF_HOST_MEMORY;
memset(enc, 0u, sizeof(*enc));
enc->dev = device;
kk_encoder_start_internal(&enc->main, device, queue->main.mtl_handle);
kk_encoder_start_internal(&enc->pre_gfx, device, queue->pre_gfx.mtl_handle);
enc->event = mtl_new_event(device);
util_dynarray_init(&enc->imm_writes, NULL);
util_dynarray_init(&enc->resident_buffers, NULL);
util_dynarray_init(&enc->copy_query_pool_result_infos, NULL);
*encoder = enc;
return VK_SUCCESS;
}
mtl_render_encoder *
kk_encoder_start_render(struct kk_cmd_buffer *cmd,
mtl_render_pass_descriptor *descriptor,
uint32_t view_mask)
{
struct kk_encoder *encoder = cmd->encoder;
/* We must not already be in a render encoder */
assert(encoder->main.last_used != KK_ENC_RENDER ||
encoder->main.encoder == NULL);
if (encoder->main.last_used != KK_ENC_RENDER) {
kk_encoder_signal_fence_and_end(cmd);
/* Before we start any render operation we need to ensure we have the
* requried signals to insert pre_gfx execution before the render encoder
* in case we need to insert commands to massage input data for things
* like triangle fans. For this, we signal the value pre_gfx will wait on,
* and we wait on the value pre_gfx will signal once completed.
*/
encoder->signal_value_pre_gfx = encoder->event_value;
mtl_encode_signal_event(encoder->main.cmd_buffer, encoder->event,
++encoder->event_value);
encoder->wait_value_pre_gfx = encoder->event_value;
mtl_encode_wait_for_event(encoder->main.cmd_buffer, encoder->event,
++encoder->event_value);
encoder->main.encoder = mtl_new_render_command_encoder_with_descriptor(
encoder->main.cmd_buffer, descriptor);
if (encoder->main.wait_fence) {
mtl_render_wait_for_fence(
encoder->main.encoder,
util_dynarray_top(&encoder->main.fences, mtl_fence *));
encoder->main.wait_fence = false;
}
uint32_t layer_ids[KK_MAX_MULTIVIEW_VIEW_COUNT] = {};
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);
encoder->main.user_heap_hash = UINT32_MAX;
/* Bind read only data aka samplers' argument buffer. */
struct kk_device *dev = kk_cmd_buffer_device(cmd);
mtl_set_vertex_buffer(encoder->main.encoder, dev->samplers.table.bo->map,
0u, 1u);
mtl_set_fragment_buffer(encoder->main.encoder,
dev->samplers.table.bo->map, 0u, 1u);
}
encoder->main.last_used = KK_ENC_RENDER;
return encoder->main.encoder;
}
mtl_compute_encoder *
kk_encoder_start_compute(struct kk_cmd_buffer *cmd)
{
struct kk_encoder *encoder = cmd->encoder;
/* We must not already be in a render encoder */
assert(encoder->main.last_used != KK_ENC_RENDER ||
encoder->main.encoder == NULL);
struct kk_encoder_internal *enc = &encoder->main;
if (encoder->main.last_used != KK_ENC_COMPUTE) {
kk_encoder_signal_fence_and_end(cmd);
enc->encoder = mtl_new_compute_command_encoder(enc->cmd_buffer);
if (enc->wait_fence) {
mtl_compute_wait_for_fence(
enc->encoder, util_dynarray_top(&enc->fences, mtl_fence *));
enc->wait_fence = false;
}
enc->user_heap_hash = UINT32_MAX;
/* Bind read only data aka samplers' argument buffer. */
struct kk_device *dev = kk_cmd_buffer_device(cmd);
mtl_compute_set_buffer(enc->encoder, dev->samplers.table.bo->map, 0u, 1u);
}
encoder->main.last_used = KK_ENC_COMPUTE;
return encoder->main.encoder;
}
mtl_compute_encoder *
kk_encoder_start_blit(struct kk_cmd_buffer *cmd)
{
struct kk_encoder *encoder = cmd->encoder;
/* We must not already be in a render encoder */
assert(encoder->main.last_used != KK_ENC_RENDER ||
encoder->main.encoder == NULL);
struct kk_encoder_internal *enc = &encoder->main;
if (encoder->main.last_used != KK_ENC_BLIT) {
kk_encoder_signal_fence_and_end(cmd);
enc->encoder = mtl_new_blit_command_encoder(enc->cmd_buffer);
if (enc->wait_fence) {
mtl_compute_wait_for_fence(
enc->encoder, util_dynarray_top(&enc->fences, mtl_fence *));
enc->wait_fence = false;
}
}
encoder->main.last_used = KK_ENC_BLIT;
return encoder->main.encoder;
}
void
kk_encoder_end(struct kk_cmd_buffer *cmd)
{
assert(cmd);
kk_encoder_signal_fence_and_end(cmd);
/* Let remaining render encoders run without waiting since we are done */
mtl_encode_signal_event(cmd->encoder->pre_gfx.cmd_buffer,
cmd->encoder->event, cmd->encoder->event_value);
}
struct kk_imm_write_push {
uint64_t buffer_address;
uint32_t count;
};
void
upload_queue_writes(struct kk_cmd_buffer *cmd)
{
struct kk_encoder *enc = cmd->encoder;
struct kk_device *dev = kk_cmd_buffer_device(cmd);
uint32_t count = util_dynarray_num_elements(&enc->imm_writes, uint64_t) / 2u;
if (count != 0) {
mtl_compute_encoder *compute = kk_compute_encoder(cmd);
struct kk_bo *bo = kk_cmd_allocate_buffer(cmd, enc->imm_writes.size, 8u);
/* kk_cmd_allocate_buffer sets the cmd buffer error so we can just exit */
if (!bo)
return;
memcpy(bo->cpu, enc->imm_writes.data, enc->imm_writes.size);
uint32_t buffer_count =
util_dynarray_num_elements(&enc->resident_buffers, mtl_buffer *);
mtl_compute_use_resource(compute, bo->map, MTL_RESOURCE_USAGE_READ);
mtl_compute_use_resources(
compute, enc->resident_buffers.data, buffer_count,
MTL_RESOURCE_USAGE_READ | MTL_RESOURCE_USAGE_WRITE);
struct kk_imm_write_push push_data = {
.buffer_address = bo->gpu,
.count = count,
};
kk_cmd_dispatch_pipeline(cmd, compute,
kk_device_lib_pipeline(dev, KK_LIB_IMM_WRITE),
&push_data, sizeof(push_data), count, 1, 1);
enc->resident_buffers.size = 0u;
enc->imm_writes.size = 0u;
}
count = util_dynarray_num_elements(&enc->copy_query_pool_result_infos,
struct kk_copy_query_pool_results_info);
if (count != 0u) {
mtl_compute_encoder *compute = kk_compute_encoder(cmd);
uint32_t buffer_count =
util_dynarray_num_elements(&enc->resident_buffers, mtl_buffer *);
mtl_compute_use_resources(
compute, enc->resident_buffers.data, buffer_count,
MTL_RESOURCE_USAGE_READ | MTL_RESOURCE_USAGE_WRITE);
for (uint32_t i = 0u; i < count; ++i) {
struct kk_copy_query_pool_results_info *push_data =
util_dynarray_element(&enc->copy_query_pool_result_infos,
struct kk_copy_query_pool_results_info, i);
kk_cmd_dispatch_pipeline(
cmd, compute, kk_device_lib_pipeline(dev, KK_LIB_COPY_QUERY),
push_data, sizeof(*push_data), push_data->query_count, 1, 1);
}
enc->resident_buffers.size = 0u;
enc->copy_query_pool_result_infos.size = 0u;
}
/* All immediate write done, reset encoder */
kk_encoder_signal_fence_and_end(cmd);
}
void
kk_encoder_signal_fence_and_end(struct kk_cmd_buffer *cmd)
{
struct kk_encoder *encoder = cmd->encoder;
/* End pre_gfx */
if (encoder->pre_gfx.encoder) {
mtl_end_encoding(encoder->pre_gfx.encoder);
mtl_release(encoder->pre_gfx.encoder);
encoder->pre_gfx.encoder = NULL;
/* We can start rendering once all pre-graphics work is done */
mtl_encode_signal_event(encoder->pre_gfx.cmd_buffer, encoder->event,
encoder->event_value);
}
assert(encoder);
enum kk_encoder_type type = encoder->main.last_used;
struct kk_encoder_internal *enc = kk_encoder_get_internal(encoder, type);
if (!enc || !enc->encoder)
return;
mtl_fence *fence = mtl_new_fence(encoder->dev);
switch (type) {
case KK_ENC_RENDER:
mtl_render_update_fence(enc->encoder, fence);
break;
case KK_ENC_COMPUTE:
mtl_compute_update_fence(enc->encoder, fence);
break;
case KK_ENC_BLIT:
mtl_blit_update_fence(enc->encoder, fence);
break;
default:
assert(0);
break;
}
mtl_end_encoding(enc->encoder);
mtl_release(enc->encoder);
enc->encoder = NULL;
enc->last_used = KK_ENC_NONE;
enc->wait_fence = true;
util_dynarray_append(&enc->fences, mtl_fence *, fence);
if (cmd->drawable) {
mtl_present_drawable(enc->cmd_buffer, cmd->drawable);
cmd->drawable = NULL;
}
upload_queue_writes(cmd);
}
static void
kk_post_execution_release_internal(struct kk_encoder_internal *encoder)
{
mtl_release(encoder->cmd_buffer);
util_dynarray_foreach(&encoder->fences, mtl_fence *, fence)
mtl_release(*fence);
util_dynarray_fini(&encoder->fences);
}
static void
kk_post_execution_release(void *data)
{
struct kk_encoder *encoder = data;
kk_post_execution_release_internal(&encoder->main);
kk_post_execution_release_internal(&encoder->pre_gfx);
mtl_release(encoder->event);
util_dynarray_fini(&encoder->imm_writes);
util_dynarray_fini(&encoder->resident_buffers);
util_dynarray_fini(&encoder->copy_query_pool_result_infos);
free(encoder);
}
void
kk_encoder_submit(struct kk_encoder *encoder)
{
assert(encoder);
mtl_add_completed_handler(encoder->main.cmd_buffer,
kk_post_execution_release, encoder);
mtl_command_buffer_commit(encoder->pre_gfx.cmd_buffer);
mtl_command_buffer_commit(encoder->main.cmd_buffer);
}
mtl_render_encoder *
kk_render_encoder(struct kk_cmd_buffer *cmd)
{
struct kk_encoder *encoder = cmd->encoder;
/* Render encoders are created at vkBeginRendering only */
assert(encoder->main.last_used == KK_ENC_RENDER && encoder->main.encoder);
return (mtl_render_encoder *)encoder->main.encoder;
}
mtl_compute_encoder *
kk_compute_encoder(struct kk_cmd_buffer *cmd)
{
struct kk_encoder *encoder = cmd->encoder;
return encoder->main.last_used == KK_ENC_COMPUTE
? (mtl_blit_encoder *)encoder->main.encoder
: kk_encoder_start_compute(cmd);
}
mtl_blit_encoder *
kk_blit_encoder(struct kk_cmd_buffer *cmd)
{
struct kk_encoder *encoder = cmd->encoder;
return encoder->main.last_used == KK_ENC_BLIT
? (mtl_blit_encoder *)encoder->main.encoder
: kk_encoder_start_blit(cmd);
}
struct kk_encoder_internal *
kk_encoder_get_internal(struct kk_encoder *encoder, enum kk_encoder_type type)
{
switch (type) {
case KK_ENC_NONE:
assert(encoder->main.last_used == KK_ENC_NONE);
return NULL;
case KK_ENC_RENDER:
assert(encoder->main.last_used == KK_ENC_RENDER);
return &encoder->main;
case KK_ENC_COMPUTE:
assert(encoder->main.last_used == KK_ENC_COMPUTE);
return &encoder->main;
case KK_ENC_BLIT:
assert(encoder->main.last_used == KK_ENC_BLIT);
return &encoder->main;
default:
assert(0);
return NULL;
}
}
static mtl_compute_encoder *
kk_encoder_pre_gfx_encoder(struct kk_encoder *encoder)
{
if (!encoder->pre_gfx.encoder) {
/* Fast-forward all previous render encoders and wait for the last one */
mtl_encode_signal_event(encoder->pre_gfx.cmd_buffer, encoder->event,
encoder->signal_value_pre_gfx);
mtl_encode_wait_for_event(encoder->pre_gfx.cmd_buffer, encoder->event,
encoder->wait_value_pre_gfx);
encoder->pre_gfx.encoder =
mtl_new_compute_command_encoder(encoder->pre_gfx.cmd_buffer);
}
return encoder->pre_gfx.encoder;
}
struct kk_triangle_fan_info {
uint64_t index_buffer;
uint64_t out_ptr;
uint64_t in_draw;
uint64_t out_draw;
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;
};
static void
kk_encoder_render_triangle_fan_common(struct kk_cmd_buffer *cmd,
struct kk_triangle_fan_info *info,
mtl_buffer *indirect, mtl_buffer *index,
uint32_t index_count,
uint32_t in_el_size_B,
uint32_t out_el_size_B)
{
uint32_t index_buffer_size_B = index_count * out_el_size_B;
uint32_t buffer_size_B =
sizeof(VkDrawIndexedIndirectCommand) + index_buffer_size_B;
struct kk_bo *index_buffer =
kk_cmd_allocate_buffer(cmd, buffer_size_B, out_el_size_B);
if (!index_buffer)
return;
info->out_ptr = index_buffer->gpu + sizeof(VkDrawIndexedIndirectCommand);
info->out_draw = index_buffer->gpu;
info->in_el_size_B = in_el_size_B;
info->out_el_size_B = out_el_size_B;
info->flatshade_first = true;
mtl_compute_encoder *encoder = kk_encoder_pre_gfx_encoder(cmd->encoder);
if (index)
mtl_compute_use_resource(encoder, index, MTL_RESOURCE_USAGE_READ);
mtl_compute_use_resource(encoder, indirect, MTL_RESOURCE_USAGE_READ);
mtl_compute_use_resource(encoder, index_buffer->map,
MTL_RESOURCE_USAGE_WRITE);
struct kk_device *dev = kk_cmd_buffer_device(cmd);
kk_cmd_dispatch_pipeline(cmd, encoder,
kk_device_lib_pipeline(dev, KK_LIB_TRIANGLE_FAN),
info, sizeof(*info), 1u, 1u, 1u);
enum mtl_index_type index_type =
index_size_in_bytes_to_mtl_index_type(out_el_size_B);
mtl_render_encoder *enc = kk_render_encoder(cmd);
mtl_draw_indexed_primitives_indirect(
enc, cmd->state.gfx.primitive_type, index_type, index_buffer->map,
sizeof(VkDrawIndexedIndirectCommand), index_buffer->map, 0u);
}
void
kk_encoder_render_triangle_fan_indirect(struct kk_cmd_buffer *cmd,
mtl_buffer *indirect, uint64_t offset)
{
enum mesa_prim mode = cmd->state.gfx.prim;
uint32_t decomposed_index_count =
u_decomposed_prims_for_vertices(mode, cmd->state.gfx.vb.max_vertices) *
mesa_vertices_per_prim(mode);
uint32_t el_size_B = decomposed_index_count < UINT16_MAX ? 2u : 4u;
struct kk_triangle_fan_info info = {
.in_draw = mtl_buffer_get_gpu_address(indirect) + offset,
.restart_index = UINT32_MAX, /* No restart */
.mode = mode,
};
kk_encoder_render_triangle_fan_common(
cmd, &info, indirect, NULL, decomposed_index_count, el_size_B, el_size_B);
}
void
kk_encoder_render_triangle_fan_indexed_indirect(struct kk_cmd_buffer *cmd,
mtl_buffer *indirect,
uint64_t offset,
bool increase_el_size)
{
uint32_t el_size_B = cmd->state.gfx.index.bytes_per_index;
enum mesa_prim mode = cmd->state.gfx.prim;
uint32_t max_index_count =
(mtl_buffer_get_length(cmd->state.gfx.index.handle) -
cmd->state.gfx.index.offset) /
el_size_B;
uint32_t decomposed_index_count =
u_decomposed_prims_for_vertices(mode, max_index_count) *
mesa_vertices_per_prim(mode);
struct kk_triangle_fan_info info = {
.index_buffer = mtl_buffer_get_gpu_address(cmd->state.gfx.index.handle) +
cmd->state.gfx.index.offset,
.in_draw = mtl_buffer_get_gpu_address(indirect) + offset,
.restart_index =
increase_el_size ? UINT32_MAX : cmd->state.gfx.index.restart,
.index_buffer_size_el = max_index_count,
.mode = mode,
};
uint32_t out_el_size_B = increase_el_size ? sizeof(uint32_t) : el_size_B;
kk_encoder_render_triangle_fan_common(
cmd, &info, indirect, cmd->state.gfx.index.handle, decomposed_index_count,
el_size_B, out_el_size_B);
}

View file

@ -0,0 +1,125 @@
/*
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_ENCODER_H
#define KK_ENCODER_H 1
#include "kosmickrisp/bridge/mtl_types.h"
#include "util/u_dynarray.h"
#include "vulkan/vulkan.h"
struct kk_queue;
struct kk_cmd_buffer;
enum kk_encoder_type {
KK_ENC_NONE = 0,
KK_ENC_RENDER = BITFIELD_BIT(0),
KK_ENC_COMPUTE = BITFIELD_BIT(1),
KK_ENC_BLIT = BITFIELD_BIT(2),
KK_ENC_ALL = (KK_ENC_RENDER | KK_ENC_COMPUTE | KK_ENC_BLIT),
KK_ENC_COUNT = 3u,
};
struct kk_encoder_internal {
mtl_command_buffer *cmd_buffer;
mtl_command_encoder *encoder;
/* Used to know if we need to make heaps resident again */
uint32_t user_heap_hash;
/* Need to track last used to we can converge at submission */
enum kk_encoder_type last_used;
/* Used to synchronize between passes inside the same command buffer */
struct util_dynarray fences;
/* Tracks if we need to wait on the last fence present in fences at the start
* of the pass */
bool wait_fence;
};
struct kk_copy_query_pool_results_info {
uint64_t availability;
uint64_t results;
uint64_t indices;
uint64_t dst_addr;
uint64_t dst_stride;
uint32_t first_query;
VkQueryResultFlagBits flags;
uint16_t reports_per_query;
uint32_t query_count;
};
struct kk_encoder {
mtl_device *dev;
struct kk_encoder_internal main;
/* Compute only for pre gfx required work */
struct kk_encoder_internal pre_gfx;
/* Used to synchronize between main and pre_gfx encoders */
mtl_event *event;
uint64_t event_value;
/* Track what values pre_gfx must wait/signal before starting the encoding */
uint64_t wait_value_pre_gfx;
uint64_t signal_value_pre_gfx;
/* uint64_t pairs with first being the address, second being the value to
* write */
struct util_dynarray imm_writes;
/* mtl_buffers (destination buffers) so we can make them resident before the
* dispatch */
struct util_dynarray resident_buffers;
/* Array of kk_copy_quer_pool_results_info structs */
struct util_dynarray copy_query_pool_result_infos;
};
/* Allocates encoder and initialises/creates all resources required to start
* recording commands into the multiple encoders */
VkResult kk_encoder_init(mtl_device *device, struct kk_queue *queue,
struct kk_encoder **encoder);
/* Submits all command buffers and releases encoder memory. Requires all command
* buffers in the encoder to be linked to the last one used so the post
* execution callback is called once all are done */
void kk_encoder_submit(struct kk_encoder *encoder);
mtl_render_encoder *
kk_encoder_start_render(struct kk_cmd_buffer *cmd,
mtl_render_pass_descriptor *descriptor,
uint32_t view_mask);
mtl_compute_encoder *kk_encoder_start_compute(struct kk_cmd_buffer *cmd);
mtl_compute_encoder *kk_encoder_start_blit(struct kk_cmd_buffer *cmd);
/* Ends encoding on all command buffers */
void kk_encoder_end(struct kk_cmd_buffer *cmd);
/* Creates a fence and signals it inside the encoder, then ends encoding */
void kk_encoder_signal_fence_and_end(struct kk_cmd_buffer *cmd);
mtl_render_encoder *kk_render_encoder(struct kk_cmd_buffer *cmd);
mtl_compute_encoder *kk_compute_encoder(struct kk_cmd_buffer *cmd);
mtl_blit_encoder *kk_blit_encoder(struct kk_cmd_buffer *cmd);
struct kk_encoder_internal *kk_encoder_get_internal(struct kk_encoder *encoder,
enum kk_encoder_type type);
void upload_queue_writes(struct kk_cmd_buffer *cmd);
void kk_encoder_render_triangle_fan_indirect(struct kk_cmd_buffer *cmd,
mtl_buffer *indirect,
uint64_t offset);
void kk_encoder_render_triangle_fan_indexed_indirect(struct kk_cmd_buffer *cmd,
mtl_buffer *indirect,
uint64_t offset,
bool increase_el_size);
#endif /* KK_ENCODER_H */

View file

@ -0,0 +1,143 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_event.h"
#include "kk_bo.h"
#include "kk_cmd_buffer.h"
#include "kk_device.h"
#include "kk_encoder.h"
#include "kk_entrypoints.h"
#define KK_EVENT_MEM_SIZE sizeof(uint64_t)
VKAPI_ATTR VkResult VKAPI_CALL
kk_CreateEvent(VkDevice device, const VkEventCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator, VkEvent *pEvent)
{
VK_FROM_HANDLE(kk_device, dev, device);
struct kk_event *event;
VkResult result = VK_SUCCESS;
event = vk_object_zalloc(&dev->vk, pAllocator, sizeof(*event),
VK_OBJECT_TYPE_EVENT);
if (!event)
return vk_error(dev, VK_ERROR_OUT_OF_HOST_MEMORY);
/* TODO_KOSMICKRISP Bring back the heap. */
result = kk_alloc_bo(dev, &dev->vk.base, KK_EVENT_MEM_SIZE,
KK_EVENT_MEM_SIZE, &event->bo);
if (result != VK_SUCCESS) {
vk_object_free(&dev->vk, pAllocator, event);
return result;
}
event->status = event->bo->cpu;
event->addr = event->bo->gpu;
*event->status = VK_EVENT_RESET;
*pEvent = kk_event_to_handle(event);
return result;
}
VKAPI_ATTR void VKAPI_CALL
kk_DestroyEvent(VkDevice device, VkEvent _event,
const VkAllocationCallbacks *pAllocator)
{
VK_FROM_HANDLE(kk_device, dev, device);
VK_FROM_HANDLE(kk_event, event, _event);
if (!event)
return;
kk_destroy_bo(dev, event->bo);
vk_object_free(&dev->vk, pAllocator, event);
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_GetEventStatus(VkDevice device, VkEvent _event)
{
VK_FROM_HANDLE(kk_event, event, _event);
return *event->status;
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_SetEvent(VkDevice device, VkEvent _event)
{
VK_FROM_HANDLE(kk_event, event, _event);
*event->status = VK_EVENT_SET;
return VK_SUCCESS;
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_ResetEvent(VkDevice device, VkEvent _event)
{
VK_FROM_HANDLE(kk_event, event, _event);
*event->status = VK_EVENT_RESET;
return VK_SUCCESS;
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdSetEvent2(VkCommandBuffer commandBuffer, VkEvent _event,
const VkDependencyInfo *pDependencyInfo)
{
VK_FROM_HANDLE(kk_event, event, _event);
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
enum kk_encoder_type last_used = cmd->encoder->main.last_used;
kk_cmd_write(cmd, event->bo->map, event->addr, VK_EVENT_SET);
if (last_used != KK_ENC_NONE)
kk_encoder_signal_fence_and_end(cmd);
else
upload_queue_writes(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);
}
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdResetEvent2(VkCommandBuffer commandBuffer, VkEvent _event,
VkPipelineStageFlags2 stageMask)
{
VK_FROM_HANDLE(kk_event, event, _event);
VK_FROM_HANDLE(kk_cmd_buffer, cmd, commandBuffer);
enum kk_encoder_type last_used = cmd->encoder->main.last_used;
kk_cmd_write(cmd, event->bo->map, event->addr, VK_EVENT_RESET);
if (last_used != KK_ENC_NONE)
kk_encoder_signal_fence_and_end(cmd);
else
upload_queue_writes(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);
}
}
VKAPI_ATTR void VKAPI_CALL
kk_CmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eventCount,
const VkEvent *pEvents,
const VkDependencyInfo *pDependencyInfos)
{
/* We do nothing, event should already be set by the time we are here. */
}

View file

@ -0,0 +1,27 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_EVENT_H
#define KK_EVENT_H 1
#include "kk_private.h"
#include "vk_object.h"
struct kk_bo;
struct kk_event {
struct vk_object_base base;
struct kk_bo *bo;
uint64_t addr;
uint64_t *status;
};
VK_DEFINE_NONDISP_HANDLE_CASTS(kk_event, base, VkEvent, VK_OBJECT_TYPE_EVENT)
#endif /* KK_EVENT_H */

View file

@ -0,0 +1,359 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_format.h"
#include "kk_buffer_view.h"
#include "kk_entrypoints.h"
#include "kk_image.h"
#include "kk_physical_device.h"
#include "kosmickrisp/bridge/mtl_format.h"
#include "vk_enum_defines.h"
#include "vk_format.h"
#define MTL_FMT_ALL_NO_ATOMIC(width) \
.bit_widths = width, .filter = 1u, .write = 1u, .color = 1u, .blend = 1u, \
.msaa = 1u, .resolve = 1u, .sparse = 1u, .atomic = 0u
// Filter, Write, Color, Blend, MSAA, Sparse
#define MTL_FMT_FWCBMS(width) \
.bit_widths = width, .filter = 1u, .write = 1u, .color = 1u, .blend = 1u, \
.msaa = 1u, .resolve = 0u, .sparse = 1u, .atomic = 0u
// Filter, Color, Blend, MSAA, Resolve, Sparse
#define MTL_FMT_FCBMRS(width) \
.bit_widths = width, .filter = 1u, .write = 0u, .color = 1u, .blend = 1u, \
.msaa = 1u, .resolve = 1u, .sparse = 1u, .atomic = 0u
// Filter, Write, Color, Blend, MSAA
#define MTL_FMT_FWCBM(width) \
.bit_widths = width, .filter = 1u, .write = 1u, .color = 1u, .blend = 1u, \
.msaa = 1u, .resolve = 0u, .sparse = 0u, .atomic = 0u
// Write, Color, Blend, MSAA, Sparse
#define MTL_FMT_WCBMS(width) \
.bit_widths = width, .filter = 0u, .write = 1u, .color = 1u, .blend = 1u, \
.msaa = 1u, .resolve = 0u, .sparse = 0u, .atomic = 0u
// Write, Color, MSAA, Sparse
#define MTL_FMT_WCMS(width) \
.bit_widths = width, .filter = 0u, .write = 1u, .color = 1u, .blend = 0u, \
.msaa = 1u, .resolve = 0u, .sparse = 1u, .atomic = 0u
// Write, Color, Sparse, Atomic
#define MTL_FMT_WCSA(width) \
.bit_widths = width, .filter = 0u, .write = 1u, .color = 1u, .blend = 0u, \
.msaa = 0u, .resolve = 0u, .sparse = 1u, .atomic = 1u
// Write, Color, Sparse
#define MTL_FMT_WCS(width) \
.bit_widths = width, .filter = 0u, .write = 1u, .color = 1u, .blend = 0u, \
.msaa = 0u, .resolve = 0u, .sparse = 1u, .atomic = 0u
// Filter, MSAA, Resolve
#define MTL_FMT_FMR(width) \
.bit_widths = width, .filter = 1u, .write = 0u, .color = 0u, .blend = 0u, \
.msaa = 1u, .resolve = 1u, .sparse = 0u, .atomic = 0u
// Filter, Sparse
#define MTL_FMT_FS(width) \
.bit_widths = width, .filter = 1u, .write = 0u, .color = 0u, .blend = 0u, \
.msaa = 0u, .resolve = 0u, .sparse = 1u, .atomic = 0u
// MSAA, Resolve
#define MTL_FMT_MR(width) \
.bit_widths = width, .filter = 0u, .write = 0u, .color = 0u, .blend = 0u, \
.msaa = 1u, .resolve = 1u, .sparse = 0u, .atomic = 0u
// MSAA
#define MTL_FMT_M(width) \
.bit_widths = width, .filter = 0u, .write = 0u, .color = 0u, .blend = 0u, \
.msaa = 1u, .resolve = 0u, .sparse = 0u, .atomic = 0u
#define MTL_FMT_TB_ALL \
.texel_buffer = { \
.write = 1u, \
.read = 1u, \
.read_write = 1u, \
}
#define MTL_FMT_TB_WR \
.texel_buffer = { \
.write = 1u, \
.read = 1u, \
.read_write = 0u, \
}
#define MTL_FMT_TB_R \
.texel_buffer = { \
.write = 0u, \
.read = 1u, \
.read_write = 0u, \
}
#define MTL_FMT_TB_NONE \
.texel_buffer = { \
.write = 0u, \
.read = 0u, \
.read_write = 0u, \
}
#define MTL_SWIZZLE_IDENTITY \
.swizzle = { \
.red = PIPE_SWIZZLE_X, \
.green = PIPE_SWIZZLE_Y, \
.blue = PIPE_SWIZZLE_Z, \
.alpha = PIPE_SWIZZLE_W, \
}
#define MTL_SWIZZLE_ABGR \
.swizzle = { \
.red = PIPE_SWIZZLE_W, \
.green = PIPE_SWIZZLE_Z, \
.blue = PIPE_SWIZZLE_Y, \
.alpha = PIPE_SWIZZLE_X, \
}
#define MTL_SWIZZLE_BGRA \
.swizzle = { \
.red = PIPE_SWIZZLE_Z, \
.green = PIPE_SWIZZLE_Y, \
.blue = PIPE_SWIZZLE_X, \
.alpha = PIPE_SWIZZLE_W, \
}
#define MTL_FMT(pipe_format, mtl_format, swizzle, capabilities, \
texel_buffer_capabilities, native) \
[PIPE_FORMAT_## \
pipe_format] = {.mtl_pixel_format = MTL_PIXEL_FORMAT_##mtl_format, \
swizzle, \
capabilities, \
texel_buffer_capabilities, \
.is_native = native}
#define MTL_FMT_NATIVE(format, capabilities, texel_buffer_capabilities) \
[PIPE_FORMAT_##format] = {.mtl_pixel_format = MTL_PIXEL_FORMAT_##format, \
MTL_SWIZZLE_IDENTITY, \
capabilities, \
texel_buffer_capabilities, \
.is_native = 1}
static const struct kk_va_format kk_vf_formats[] = {
// 8-bit formats
MTL_FMT_NATIVE(R8_UNORM, MTL_FMT_ALL_NO_ATOMIC(8), MTL_FMT_TB_ALL),
MTL_FMT_NATIVE(A8_UNORM, MTL_FMT_ALL_NO_ATOMIC(8), MTL_FMT_TB_ALL),
MTL_FMT_NATIVE(R8_SRGB, MTL_FMT_ALL_NO_ATOMIC(8), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(R8_SNORM, MTL_FMT_ALL_NO_ATOMIC(8), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R8_UINT, MTL_FMT_WCMS(8), MTL_FMT_TB_ALL),
MTL_FMT_NATIVE(R8_SINT, MTL_FMT_WCMS(8), MTL_FMT_TB_ALL),
// 16-bit formats
MTL_FMT_NATIVE(R16_UNORM, MTL_FMT_FWCBMS(16), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R16_SNORM, MTL_FMT_FWCBMS(16), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R16_UINT, MTL_FMT_WCMS(16), MTL_FMT_TB_ALL),
MTL_FMT_NATIVE(R16_SINT, MTL_FMT_WCMS(16), MTL_FMT_TB_ALL),
MTL_FMT_NATIVE(R16_FLOAT, MTL_FMT_ALL_NO_ATOMIC(16), MTL_FMT_TB_ALL),
MTL_FMT_NATIVE(R8G8_UNORM, MTL_FMT_ALL_NO_ATOMIC(16), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R8G8_SNORM, MTL_FMT_ALL_NO_ATOMIC(16), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R8G8_SRGB, MTL_FMT_ALL_NO_ATOMIC(16), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(R8G8_UINT, MTL_FMT_WCMS(16), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R8G8_SINT, MTL_FMT_WCMS(16), MTL_FMT_TB_WR),
// 32-bit formats
MTL_FMT_NATIVE(R32_UINT, MTL_FMT_WCSA(32), MTL_FMT_TB_ALL),
MTL_FMT_NATIVE(R32_SINT, MTL_FMT_WCSA(32), MTL_FMT_TB_ALL),
MTL_FMT_NATIVE(R32_FLOAT, MTL_FMT_WCBMS(32), MTL_FMT_TB_ALL),
MTL_FMT_NATIVE(R16G16_UNORM, MTL_FMT_FWCBMS(32), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R16G16_SNORM, MTL_FMT_FWCBMS(32), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R16G16_UINT, MTL_FMT_WCMS(32), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R16G16_SINT, MTL_FMT_WCMS(32), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R16G16_FLOAT, MTL_FMT_ALL_NO_ATOMIC(32), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R8G8B8A8_UNORM, MTL_FMT_ALL_NO_ATOMIC(32), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R8G8B8A8_SNORM, MTL_FMT_ALL_NO_ATOMIC(32), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R8G8B8A8_SRGB, MTL_FMT_ALL_NO_ATOMIC(32), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(R8G8B8A8_UINT, MTL_FMT_WCMS(32), MTL_FMT_TB_ALL),
MTL_FMT_NATIVE(R8G8B8A8_SINT, MTL_FMT_WCMS(32), MTL_FMT_TB_ALL),
MTL_FMT_NATIVE(B8G8R8A8_UNORM, MTL_FMT_ALL_NO_ATOMIC(32), MTL_FMT_TB_R),
MTL_FMT_NATIVE(B8G8R8A8_SRGB, MTL_FMT_ALL_NO_ATOMIC(32), MTL_FMT_TB_NONE),
// 64-bit formats
MTL_FMT_NATIVE(R32G32_UINT, MTL_FMT_WCMS(64), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R32G32_SINT, MTL_FMT_WCMS(64), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R32G32_FLOAT, MTL_FMT_WCBMS(64), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R16G16B16A16_UNORM, MTL_FMT_FWCBMS(64), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R16G16B16A16_SNORM, MTL_FMT_FWCBMS(64), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R16G16B16A16_UINT, MTL_FMT_WCMS(64), MTL_FMT_TB_ALL),
MTL_FMT_NATIVE(R16G16B16A16_SINT, MTL_FMT_WCMS(64), MTL_FMT_TB_ALL),
MTL_FMT_NATIVE(R16G16B16A16_FLOAT, MTL_FMT_ALL_NO_ATOMIC(64),
MTL_FMT_TB_ALL),
// 128-bit formats
MTL_FMT_NATIVE(R32G32B32A32_UINT, MTL_FMT_WCS(128), MTL_FMT_TB_ALL),
MTL_FMT_NATIVE(R32G32B32A32_SINT, MTL_FMT_WCS(128), MTL_FMT_TB_ALL),
MTL_FMT_NATIVE(R32G32B32A32_FLOAT, MTL_FMT_WCMS(128), MTL_FMT_TB_ALL),
// 16-bit packed formats
MTL_FMT_NATIVE(B5G6R5_UNORM, MTL_FMT_FCBMRS(16), MTL_FMT_TB_NONE),
/* Hardware has issues with border color opaque black, and since it's not
* required by Vulkan, we can just disable it.
*/
/* MTL_FMT_NATIVE(A1B5G5R5_UNORM, MTL_FMT_FCBMRS(16), MTL_FMT_TB_NONE), */
MTL_FMT_NATIVE(A4B4G4R4_UNORM, MTL_FMT_FCBMRS(16), MTL_FMT_TB_NONE),
MTL_FMT(R4G4B4A4_UNORM, A4B4G4R4_UNORM, MTL_SWIZZLE_ABGR, MTL_FMT_FCBMRS(16),
MTL_FMT_TB_NONE, false),
MTL_FMT(A4R4G4B4_UNORM, A4B4G4R4_UNORM, MTL_SWIZZLE_BGRA, MTL_FMT_FCBMRS(16),
MTL_FMT_TB_NONE, false),
MTL_FMT_NATIVE(B5G5R5A1_UNORM, MTL_FMT_FCBMRS(16), MTL_FMT_TB_NONE),
// 32-bit packed formats
MTL_FMT_NATIVE(R10G10B10A2_UNORM, MTL_FMT_ALL_NO_ATOMIC(32), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(B10G10R10A2_UNORM, MTL_FMT_ALL_NO_ATOMIC(32),
MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(R10G10B10A2_UINT, MTL_FMT_WCMS(32), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R11G11B10_FLOAT, MTL_FMT_ALL_NO_ATOMIC(32), MTL_FMT_TB_WR),
MTL_FMT_NATIVE(R9G9B9E5_FLOAT, MTL_FMT_ALL_NO_ATOMIC(32), MTL_FMT_TB_NONE),
// ASTC formats
MTL_FMT_NATIVE(ASTC_4x4, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_5x4, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_5x5, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_6x5, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_6x6, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_8x5, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_8x6, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_8x8, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_10x5, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_10x6, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_10x8, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_10x10, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_12x10, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_12x12, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_4x4_SRGB, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_5x4_SRGB, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_5x5_SRGB, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_6x5_SRGB, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_6x6_SRGB, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_8x5_SRGB, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_8x6_SRGB, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_8x8_SRGB, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_10x5_SRGB, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_10x6_SRGB, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_10x8_SRGB, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_10x10_SRGB, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_12x10_SRGB, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ASTC_12x12_SRGB, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
// EAC/ETC formats
MTL_FMT_NATIVE(ETC2_R11_UNORM, MTL_FMT_FS(64), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ETC2_R11_SNORM, MTL_FMT_FS(64), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ETC2_RG11_UNORM, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ETC2_RG11_SNORM, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ETC2_RGBA8, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ETC2_SRGBA8, MTL_FMT_FS(128), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ETC2_RGB8, MTL_FMT_FS(64), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ETC2_SRGB8, MTL_FMT_FS(64), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ETC2_RGB8A1, MTL_FMT_FS(64), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(ETC2_SRGB8A1, MTL_FMT_FS(64), MTL_FMT_TB_NONE),
// Compressed PVRTC, HDR ASTC, BC TODO_KOSMICKRISP
// YUV formats TODO_KOSMICKRISP
// Extended range and wide color formats TODO_KOSMICKRISP
// Depth and stencil formats
MTL_FMT_NATIVE(Z16_UNORM, MTL_FMT_FMR(16), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(Z32_FLOAT, MTL_FMT_MR(32), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(S8_UINT, MTL_FMT_M(8), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(Z32_FLOAT_S8X24_UINT, MTL_FMT_MR(64), MTL_FMT_TB_NONE),
MTL_FMT_NATIVE(X32_S8X24_UINT, MTL_FMT_MR(64), MTL_FMT_TB_NONE),
};
#undef MTL_FMT_NATIVE
#undef MTL_FMT
#undef MTL_SWIZZLE_BGRA
#undef MTL_SWIZZLE_ABGR
#undef MTL_SWIZZLE_IDENTITY
#undef MTL_FMT_ALL_NO_ATOMIC
#undef MTL_FMT_FWCBMS
#undef MTL_FMT_FCBMRS
#undef MTL_FMT_FWCBM
#undef MTL_FMT_WCBMS
#undef MTL_FMT_WCMS
#undef MTL_FMT_WCSA
#undef MTL_FMT_WCS
#undef MTL_FMT_FMR
#undef MTL_FMT_FS
#undef MTL_FMT_MR
#undef MTL_FMT_M
#undef MTL_FMT_TB_ALL
#undef MTL_FMT_TB_WR
#undef MTL_FMT_TB_R
#undef MTL_FMT_TB_NONE
const struct kk_va_format *
kk_get_va_format(enum pipe_format format)
{
if (format >= ARRAY_SIZE(kk_vf_formats))
return NULL;
if (kk_vf_formats[format].bit_widths == 0)
return NULL;
return &kk_vf_formats[format];
}
enum mtl_pixel_format
vk_format_to_mtl_pixel_format(VkFormat vkformat)
{
enum pipe_format format = vk_format_to_pipe_format(vkformat);
const struct kk_va_format *supported_format = kk_get_va_format(format);
assert(supported_format);
return supported_format->mtl_pixel_format;
}
VKAPI_ATTR void VKAPI_CALL
kk_GetPhysicalDeviceFormatProperties2(VkPhysicalDevice physicalDevice,
VkFormat format,
VkFormatProperties2 *pFormatProperties)
{
VK_FROM_HANDLE(kk_physical_device, pdevice, physicalDevice);
VkFormatFeatureFlags2 linear2, optimal2, buffer2;
linear2 =
kk_get_image_format_features(pdevice, format, VK_IMAGE_TILING_LINEAR, 0);
optimal2 =
kk_get_image_format_features(pdevice, format, VK_IMAGE_TILING_OPTIMAL, 0);
buffer2 = kk_get_buffer_format_features(pdevice, format);
pFormatProperties->formatProperties = (VkFormatProperties){
.linearTilingFeatures = vk_format_features2_to_features(linear2),
.optimalTilingFeatures = vk_format_features2_to_features(optimal2),
.bufferFeatures = vk_format_features2_to_features(buffer2),
};
vk_foreach_struct(ext, pFormatProperties->pNext) {
switch (ext->sType) {
case VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3: {
VkFormatProperties3 *p = (void *)ext;
p->linearTilingFeatures = linear2;
p->optimalTilingFeatures = optimal2;
p->bufferFeatures = buffer2;
break;
}
default:
vk_debug_ignored_stype(ext->sType);
break;
}
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_FORMAT_H
#define KK_FORMAT_H 1
#include "kk_private.h"
#include "util/format/u_format.h"
struct kk_physical_device;
enum pipe_format;
enum mtl_pixel_format;
struct kk_va_format {
/* Would love to use enum pipe_swizzle, but it's bigger than the required
* type for util_format_compose_swizzles... */
struct {
union {
struct {
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t alpha;
};
uint8_t channels[4];
};
} swizzle;
uint32_t mtl_pixel_format;
uint8_t bit_widths;
uint8_t filter : 1;
uint8_t write : 1;
uint8_t color : 1;
uint8_t blend : 1;
uint8_t msaa : 1;
uint8_t resolve : 1;
uint8_t sparse : 1;
uint8_t atomic : 1;
struct {
uint8_t write : 1;
uint8_t read : 1;
uint8_t read_write : 1;
} texel_buffer;
uint8_t is_native : 1;
};
const struct kk_va_format *kk_get_va_format(enum pipe_format format);
enum mtl_pixel_format vk_format_to_mtl_pixel_format(enum VkFormat vkformat);
#endif /* KK_FORMAT_H */

View file

@ -0,0 +1,967 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#include "kk_image.h"
#include "kk_device.h"
#include "kk_device_memory.h"
#include "kk_entrypoints.h"
#include "kk_format.h"
#include "kk_physical_device.h"
#include "kosmickrisp/bridge/mtl_bridge.h"
#include "vk_enum_defines.h"
#include "vk_enum_to_str.h"
#include "vk_format.h"
#include "wsi_common_private.h"
static VkFormatFeatureFlags2
kk_get_image_plane_format_features(struct kk_physical_device *pdev,
VkFormat vk_format, VkImageTiling tiling,
uint64_t drm_format_mod)
{
VkFormatFeatureFlags2 features = 0;
/* Metal does not support linear tiling for compressed formats */
if (tiling == VK_IMAGE_TILING_LINEAR && vk_format_is_compressed(vk_format))
return 0;
enum pipe_format p_format = vk_format_to_pipe_format(vk_format);
if (p_format == PIPE_FORMAT_NONE)
return 0;
/* You can't tile a non-power-of-two */
if (!util_is_power_of_two_nonzero(util_format_get_blocksize(p_format)))
return 0;
const struct kk_va_format *va_format = kk_get_va_format(p_format);
if (va_format == NULL)
return 0;
// Textures can at least be sampled
features |= VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_BIT;
features |= VK_FORMAT_FEATURE_2_BLIT_SRC_BIT;
if (va_format->filter) {
features |= VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
features |=
VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_FILTER_MINMAX_BIT; // TODO_KOSMICKRISP
// Understand if
// we want to
// expose this
}
/* TODO: VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_DEPTH_COMPARISON_BIT */
if (vk_format_has_depth(vk_format)) {
features |= VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_DEPTH_COMPARISON_BIT;
}
/* We disable A8 format due to lower blend pass issues */
if (va_format->color && tiling != VK_IMAGE_TILING_LINEAR &&
vk_format != VK_FORMAT_A8_UNORM) {
features |= VK_FORMAT_FEATURE_2_COLOR_ATTACHMENT_BIT;
features |= VK_FORMAT_FEATURE_2_BLIT_DST_BIT;
// TODO_KOSMICKRISP Support snorm formats once the following spec issue is
// resolved: https://gitlab.khronos.org/vulkan/vulkan/-/issues/4293
if (!vk_format_is_snorm(vk_format))
features |= VK_FORMAT_FEATURE_2_COLOR_ATTACHMENT_BLEND_BIT;
}
if (vk_format_is_depth_or_stencil(vk_format)) {
if (tiling == VK_IMAGE_TILING_LINEAR)
return 0;
features |= VK_FORMAT_FEATURE_2_DEPTH_STENCIL_ATTACHMENT_BIT;
}
if (va_format->write) {
features |= VK_FORMAT_FEATURE_2_STORAGE_IMAGE_BIT;
}
if (va_format->atomic)
features |= VK_FORMAT_FEATURE_2_STORAGE_IMAGE_ATOMIC_BIT;
if (features != 0) {
features |= VK_FORMAT_FEATURE_2_TRANSFER_SRC_BIT;
features |= VK_FORMAT_FEATURE_2_TRANSFER_DST_BIT;
}
return features;
}
VkFormatFeatureFlags2
kk_get_image_format_features(struct kk_physical_device *pdev,
VkFormat vk_format, VkImageTiling tiling,
uint64_t drm_format_mod)
{
const struct vk_format_ycbcr_info *ycbcr_info =
vk_format_get_ycbcr_info(vk_format);
if (ycbcr_info == NULL) {
return kk_get_image_plane_format_features(pdev, vk_format, tiling,
drm_format_mod);
}
/* For multi-plane, we get the feature flags of each plane separately,
* then take their intersection as the overall format feature flags
*/
VkFormatFeatureFlags2 features = ~0ull;
bool cosited_chroma = false;
for (uint8_t plane = 0; plane < ycbcr_info->n_planes; plane++) {
const struct vk_format_ycbcr_plane *plane_info =
&ycbcr_info->planes[plane];
features &= kk_get_image_plane_format_features(pdev, plane_info->format,
tiling, drm_format_mod);
if (plane_info->denominator_scales[0] > 1 ||
plane_info->denominator_scales[1] > 1)
cosited_chroma = true;
}
if (features == 0)
return 0;
/* Uh... We really should be able to sample from YCbCr */
assert(features & VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_BIT);
assert(features & VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_FILTER_LINEAR_BIT);
/* These aren't allowed for YCbCr formats */
features &=
~(VK_FORMAT_FEATURE_2_BLIT_SRC_BIT | VK_FORMAT_FEATURE_2_BLIT_DST_BIT |
VK_FORMAT_FEATURE_2_COLOR_ATTACHMENT_BIT |
VK_FORMAT_FEATURE_2_COLOR_ATTACHMENT_BLEND_BIT |
VK_FORMAT_FEATURE_2_STORAGE_IMAGE_BIT);
/* This is supported on all YCbCr formats */
features |=
VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT;
if (ycbcr_info->n_planes > 1) {
/* DISJOINT_BIT implies that each plane has its own separate binding,
* while SEPARATE_RECONSTRUCTION_FILTER_BIT implies that luma and chroma
* each have their own, separate filters, so these two bits make sense
* for multi-planar formats only.
*
* For MIDPOINT_CHROMA_SAMPLES_BIT, NVIDIA HW on single-plane interleaved
* YCbCr defaults to COSITED_EVEN, which is inaccurate and fails tests.
* This can be fixed with a NIR tweak but for now, we only enable this bit
* for multi-plane formats. See Issue #9525 on the mesa/main tracker.
*/
features |=
VK_FORMAT_FEATURE_DISJOINT_BIT |
VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_YCBCR_CONVERSION_SEPARATE_RECONSTRUCTION_FILTER_BIT |
VK_FORMAT_FEATURE_2_MIDPOINT_CHROMA_SAMPLES_BIT;
}
if (cosited_chroma)
features |= VK_FORMAT_FEATURE_COSITED_CHROMA_SAMPLES_BIT;
return features;
}
static VkFormatFeatureFlags2
vk_image_usage_to_format_features(VkImageUsageFlagBits usage_flag)
{
assert(util_bitcount(usage_flag) == 1);
switch (usage_flag) {
case VK_IMAGE_USAGE_TRANSFER_SRC_BIT:
return VK_FORMAT_FEATURE_2_TRANSFER_SRC_BIT |
VK_FORMAT_FEATURE_BLIT_SRC_BIT;
case VK_IMAGE_USAGE_TRANSFER_DST_BIT:
return VK_FORMAT_FEATURE_2_TRANSFER_DST_BIT |
VK_FORMAT_FEATURE_BLIT_DST_BIT;
case VK_IMAGE_USAGE_SAMPLED_BIT:
return VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_BIT;
case VK_IMAGE_USAGE_STORAGE_BIT:
return VK_FORMAT_FEATURE_2_STORAGE_IMAGE_BIT;
case VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT:
return VK_FORMAT_FEATURE_2_COLOR_ATTACHMENT_BIT;
case VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT:
return VK_FORMAT_FEATURE_2_DEPTH_STENCIL_ATTACHMENT_BIT;
case VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT:
return VK_FORMAT_FEATURE_2_COLOR_ATTACHMENT_BIT |
VK_FORMAT_FEATURE_2_DEPTH_STENCIL_ATTACHMENT_BIT;
case VK_IMAGE_USAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR:
return VK_FORMAT_FEATURE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR;
default:
return 0;
}
}
uint32_t
kk_image_max_dimension(VkImageType image_type)
{
/* Values taken from Apple7
* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */
switch (image_type) {
case VK_IMAGE_TYPE_1D:
case VK_IMAGE_TYPE_2D:
return 16384;
case VK_IMAGE_TYPE_3D:
return 2048;
default:
UNREACHABLE("Invalid image type");
}
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_GetPhysicalDeviceImageFormatProperties2(
VkPhysicalDevice physicalDevice,
const VkPhysicalDeviceImageFormatInfo2 *pImageFormatInfo,
VkImageFormatProperties2 *pImageFormatProperties)
{
VK_FROM_HANDLE(kk_physical_device, pdev, physicalDevice);
const VkPhysicalDeviceExternalImageFormatInfo *external_info =
vk_find_struct_const(pImageFormatInfo->pNext,
PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO);
/* Initialize to zero in case we return VK_ERROR_FORMAT_NOT_SUPPORTED */
memset(&pImageFormatProperties->imageFormatProperties, 0,
sizeof(pImageFormatProperties->imageFormatProperties));
/* Metal does not support depth/stencil textures that are not 2D (we make 1D
* textures 2D) */
if (vk_format_is_depth_or_stencil(pImageFormatInfo->format) &&
pImageFormatInfo->type == VK_IMAGE_TYPE_3D)
return VK_ERROR_FORMAT_NOT_SUPPORTED;
/* Metal does not support EAC/ETC formats for 3D textures. */
if (util_format_is_etc(vk_format_to_pipe_format(pImageFormatInfo->format)) &&
pImageFormatInfo->type == VK_IMAGE_TYPE_3D)
return VK_ERROR_FORMAT_NOT_SUPPORTED;
/* Metal disallows reading compressed formats as uncompressed format.
* VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT is only used with
* compressed formats.
*/
if (pImageFormatInfo->flags &
VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT)
return VK_ERROR_FORMAT_NOT_SUPPORTED;
const struct vk_format_ycbcr_info *ycbcr_info =
vk_format_get_ycbcr_info(pImageFormatInfo->format);
/* For the purposes of these checks, we don't care about all the extra
* YCbCr features and we just want the accumulation of features available
* to all planes of the given format.
*/
VkFormatFeatureFlags2 features;
if (ycbcr_info == NULL) {
features = kk_get_image_plane_format_features(
pdev, pImageFormatInfo->format, pImageFormatInfo->tiling, 0u);
} else {
features = ~0ull;
assert(ycbcr_info->n_planes > 0);
for (uint8_t plane = 0; plane < ycbcr_info->n_planes; plane++) {
const VkFormat plane_format = ycbcr_info->planes[plane].format;
features &= kk_get_image_plane_format_features(
pdev, plane_format, pImageFormatInfo->tiling, 0u);
}
}
if (features == 0)
return VK_ERROR_FORMAT_NOT_SUPPORTED;
if (pImageFormatInfo->tiling == VK_IMAGE_TILING_LINEAR &&
pImageFormatInfo->type == VK_IMAGE_TYPE_3D)
return VK_ERROR_FORMAT_NOT_SUPPORTED;
/* TODO_KOSMICKRISP We could allow linear images that are used as render
* target as long as they are not used as input attachments. Main reason for
* this is that we expect arrays when rendering and reading from input
* attachments and Metal disallows arrays for linear textures.
*/
if (pImageFormatInfo->tiling == VK_IMAGE_TILING_LINEAR &&
(pImageFormatInfo->usage &
(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)))
return VK_ERROR_FORMAT_NOT_SUPPORTED;
if (ycbcr_info && pImageFormatInfo->type != VK_IMAGE_TYPE_2D)
return VK_ERROR_FORMAT_NOT_SUPPORTED;
/* Don't support sparse residency */
if ((pImageFormatInfo->flags & VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT))
return VK_ERROR_FORMAT_NOT_SUPPORTED;
/* From the Vulkan 1.3.279 spec:
*
* VUID-VkImageCreateInfo-tiling-04121
*
* "If tiling is VK_IMAGE_TILING_LINEAR, flags must not contain
* VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT"
*
* VUID-VkImageCreateInfo-imageType-00970
*
* "If imageType is VK_IMAGE_TYPE_1D, flags must not contain
* VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT"
*/
if ((pImageFormatInfo->flags & VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT) &&
(pImageFormatInfo->type == VK_IMAGE_TYPE_1D ||
pImageFormatInfo->tiling == VK_IMAGE_TILING_LINEAR))
return VK_ERROR_FORMAT_NOT_SUPPORTED;
/* From the Vulkan 1.3.279 spec:
*
* VUID-VkImageCreateInfo-flags-09403
*
* "If flags contains VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT, flags
* must not include VK_IMAGE_CREATE_SPARSE_ALIASED_BIT,
* VK_IMAGE_CREATE_SPARSE_BINDING_BIT, or
* VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT"
*/
if ((pImageFormatInfo->flags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT) &&
(pImageFormatInfo->flags & (VK_IMAGE_CREATE_SPARSE_ALIASED_BIT |
VK_IMAGE_CREATE_SPARSE_BINDING_BIT |
VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT)))
return VK_ERROR_FORMAT_NOT_SUPPORTED;
if (pImageFormatInfo->tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT &&
pImageFormatInfo->type != VK_IMAGE_TYPE_2D)
return VK_ERROR_FORMAT_NOT_SUPPORTED;
const uint32_t max_dim = kk_image_max_dimension(pImageFormatInfo->type);
assert(util_is_power_of_two_nonzero(max_dim));
uint32_t maxMipLevels = util_logbase2(max_dim) + 1;
VkExtent3D maxExtent;
uint32_t maxArraySize;
switch (pImageFormatInfo->type) {
case VK_IMAGE_TYPE_1D:
maxExtent = (VkExtent3D){max_dim, 1, 1};
maxArraySize = 2048u;
break;
case VK_IMAGE_TYPE_2D:
maxExtent = (VkExtent3D){max_dim, max_dim, 1};
maxArraySize = 2048u;
break;
case VK_IMAGE_TYPE_3D:
maxExtent = (VkExtent3D){max_dim, max_dim, max_dim};
maxArraySize = 1u;
break;
default:
UNREACHABLE("Invalid image type");
}
if (pImageFormatInfo->tiling == VK_IMAGE_TILING_LINEAR)
maxArraySize = 1;
if (ycbcr_info != NULL || pImageFormatInfo->tiling == VK_IMAGE_TILING_LINEAR)
maxMipLevels = 1;
if (pImageFormatInfo->tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT) {
maxArraySize = 1;
maxMipLevels = 1;
}
VkSampleCountFlags sampleCounts = VK_SAMPLE_COUNT_1_BIT;
if (pImageFormatInfo->tiling == VK_IMAGE_TILING_OPTIMAL &&
pImageFormatInfo->type == VK_IMAGE_TYPE_2D && ycbcr_info == NULL &&
(features & (VK_FORMAT_FEATURE_2_COLOR_ATTACHMENT_BIT |
VK_FORMAT_FEATURE_2_DEPTH_STENCIL_ATTACHMENT_BIT)) &&
!(pImageFormatInfo->flags & VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT)) {
sampleCounts =
VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_2_BIT |
// TODO_KOSMICKRISP Modify sample count based on what pdev supports
VK_SAMPLE_COUNT_4_BIT /* |
VK_SAMPLE_COUNT_8_BIT */
;
}
/* From the Vulkan 1.2.199 spec:
*
* "VK_IMAGE_CREATE_EXTENDED_USAGE_BIT specifies that the image can be
* created with usage flags that are not supported for the format the
* image is created with but are supported for at least one format a
* VkImageView created from the image can have."
*
* If VK_IMAGE_CREATE_EXTENDED_USAGE_BIT is set, views can be created with
* different usage than the image so we can't always filter on usage.
* There is one exception to this below for storage.
*/
const VkImageUsageFlags image_usage = pImageFormatInfo->usage;
VkImageUsageFlags view_usage = image_usage;
if (pImageFormatInfo->flags & VK_IMAGE_CREATE_EXTENDED_USAGE_BIT)
view_usage = 0;
u_foreach_bit(b, view_usage) {
VkFormatFeatureFlags2 usage_features =
vk_image_usage_to_format_features(1 << b);
if (usage_features && !(features & usage_features))
return VK_ERROR_FORMAT_NOT_SUPPORTED;
}
const VkExternalMemoryProperties *ext_mem_props = NULL;
if (external_info != NULL && external_info->handleType != 0) {
/* We only support heaps since that's the backing for all our memory and
* simplifies implementation */
switch (external_info->handleType) {
case VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLHEAP_BIT_EXT:
ext_mem_props = &kk_mtlheap_mem_props;
break;
default:
/* From the Vulkan 1.3.256 spec:
*
* "If handleType is not compatible with the [parameters] in
* VkPhysicalDeviceImageFormatInfo2, then
* vkGetPhysicalDeviceImageFormatProperties2 returns
* VK_ERROR_FORMAT_NOT_SUPPORTED."
*/
return vk_errorf(pdev, VK_ERROR_FORMAT_NOT_SUPPORTED,
"unsupported VkExternalMemoryHandleTypeFlagBits: %s ",
vk_ExternalMemoryHandleTypeFlagBits_to_str(
external_info->handleType));
}
}
const unsigned plane_count =
vk_format_get_plane_count(pImageFormatInfo->format);
/* From the Vulkan 1.3.259 spec, VkImageCreateInfo:
*
* VUID-VkImageCreateInfo-imageCreateFormatFeatures-02260
*
* "If format is a multi-planar format, and if imageCreateFormatFeatures
* (as defined in Image Creation Limits) does not contain
* VK_FORMAT_FEATURE_DISJOINT_BIT, then flags must not contain
* VK_IMAGE_CREATE_DISJOINT_BIT"
*
* This is satisfied trivially because we support DISJOINT on all
* multi-plane formats. Also,
*
* VUID-VkImageCreateInfo-format-01577
*
* "If format is not a multi-planar format, and flags does not include
* VK_IMAGE_CREATE_ALIAS_BIT, flags must not contain
* VK_IMAGE_CREATE_DISJOINT_BIT"
*/
if (plane_count == 1 &&
!(pImageFormatInfo->flags & VK_IMAGE_CREATE_ALIAS_BIT) &&
(pImageFormatInfo->flags & VK_IMAGE_CREATE_DISJOINT_BIT))
return VK_ERROR_FORMAT_NOT_SUPPORTED;
if (ycbcr_info &&
((pImageFormatInfo->flags & VK_IMAGE_CREATE_SPARSE_BINDING_BIT) ||
(pImageFormatInfo->flags & VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT)))
return VK_ERROR_FORMAT_NOT_SUPPORTED;
if ((pImageFormatInfo->flags & VK_IMAGE_CREATE_SPARSE_BINDING_BIT) &&
(pImageFormatInfo->usage & VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT))
return VK_ERROR_FORMAT_NOT_SUPPORTED;
pImageFormatProperties->imageFormatProperties = (VkImageFormatProperties){
.maxExtent = maxExtent,
.maxMipLevels = maxMipLevels,
.maxArrayLayers = maxArraySize,
.sampleCounts = sampleCounts,
.maxResourceSize = UINT32_MAX, /* TODO */
};
vk_foreach_struct(s, pImageFormatProperties->pNext) {
switch (s->sType) {
case VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES: {
VkExternalImageFormatProperties *p = (void *)s;
/* From the Vulkan 1.3.256 spec:
*
* "If handleType is 0, vkGetPhysicalDeviceImageFormatProperties2
* will behave as if VkPhysicalDeviceExternalImageFormatInfo was
* not present, and VkExternalImageFormatProperties will be
* ignored."
*
* This is true if and only if ext_mem_props == NULL
*/
if (ext_mem_props != NULL)
p->externalMemoryProperties = *ext_mem_props;
break;
}
case VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_IMAGE_FORMAT_PROPERTIES: {
VkSamplerYcbcrConversionImageFormatProperties *ycbcr_props = (void *)s;
ycbcr_props->combinedImageSamplerDescriptorCount = plane_count;
break;
}
case VK_STRUCTURE_TYPE_HOST_IMAGE_COPY_DEVICE_PERFORMANCE_QUERY_EXT: {
VkHostImageCopyDevicePerformanceQueryEXT *host_props = (void *)s;
host_props->optimalDeviceAccess = true;
host_props->identicalMemoryLayout = true;
break;
}
default:
vk_debug_ignored_stype(s->sType);
break;
}
}
return VK_SUCCESS;
}
VKAPI_ATTR void VKAPI_CALL
kk_GetPhysicalDeviceSparseImageFormatProperties2(
VkPhysicalDevice physicalDevice,
const VkPhysicalDeviceSparseImageFormatInfo2 *pFormatInfo,
uint32_t *pPropertyCount, VkSparseImageFormatProperties2 *pProperties)
{
*pPropertyCount = 0;
return;
}
static VkResult
kk_image_init(struct kk_device *dev, struct kk_image *image,
const VkImageCreateInfo *pCreateInfo)
{
vk_image_init(&dev->vk, &image->vk, pCreateInfo);
if ((image->vk.usage & (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) &&
image->vk.samples > 1) {
image->vk.usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
image->vk.stencil_usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
if (image->vk.usage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT)
image->vk.usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
if (image->vk.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) {
if (util_format_is_depth_or_stencil(
vk_format_to_pipe_format(image->vk.format))) {
image->vk.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
image->vk.stencil_usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
} else {
image->vk.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
}
}
image->plane_count = vk_format_get_plane_count(pCreateInfo->format);
image->disjoint = image->plane_count > 1 &&
(pCreateInfo->flags & VK_IMAGE_CREATE_DISJOINT_BIT);
const struct vk_format_ycbcr_info *ycbcr_info =
vk_format_get_ycbcr_info(pCreateInfo->format);
for (uint8_t plane = 0; plane < image->plane_count; plane++) {
VkFormat format =
ycbcr_info ? ycbcr_info->planes[plane].format : pCreateInfo->format;
const uint8_t width_scale =
ycbcr_info ? ycbcr_info->planes[plane].denominator_scales[0] : 1;
const uint8_t height_scale =
ycbcr_info ? ycbcr_info->planes[plane].denominator_scales[1] : 1;
kk_image_layout_init(dev, pCreateInfo, vk_format_to_pipe_format(format),
width_scale, height_scale,
&image->planes[plane].layout);
}
if (image->vk.format == VK_FORMAT_D32_SFLOAT_S8_UINT) {
kk_image_layout_init(dev, pCreateInfo, PIPE_FORMAT_R32_UINT, 1, 1,
&image->stencil_copy_temp.layout);
}
return VK_SUCCESS;
}
static void
kk_image_plane_size_align_B(struct kk_device *dev, const struct kk_image *image,
const struct kk_image_plane *plane,
uint64_t *size_B_out, uint64_t *align_B_out)
{
*size_B_out = plane->layout.size_B;
*align_B_out = plane->layout.align_B;
}
static void
kk_image_plane_finish(struct kk_device *dev, struct kk_image_plane *plane,
VkImageCreateFlags create_flags,
const VkAllocationCallbacks *pAllocator)
{
if (plane->mtl_handle != NULL)
mtl_release(plane->mtl_handle);
if (plane->mtl_handle_array != NULL)
mtl_release(plane->mtl_handle_array);
}
static void
kk_image_finish(struct kk_device *dev, struct kk_image *image,
const VkAllocationCallbacks *pAllocator)
{
for (uint8_t plane = 0; plane < image->plane_count; plane++) {
kk_image_plane_finish(dev, &image->planes[plane], image->vk.create_flags,
pAllocator);
}
if (image->stencil_copy_temp.layout.size_B > 0) {
kk_image_plane_finish(dev, &image->stencil_copy_temp,
image->vk.create_flags, pAllocator);
}
vk_image_finish(&image->vk);
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_CreateImage(VkDevice _device, const VkImageCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator, VkImage *pImage)
{
VK_FROM_HANDLE(kk_device, dev, _device);
struct kk_physical_device *pdev = kk_device_physical(dev);
struct kk_image *image;
VkResult result;
#ifdef KK_USE_WSI_PLATFORM
/* Ignore swapchain creation info on Android. Since we don't have an
* implementation in Mesa, we're guaranteed to access an Android object
* incorrectly.
*/
const VkImageSwapchainCreateInfoKHR *swapchain_info =
vk_find_struct_const(pCreateInfo->pNext, IMAGE_SWAPCHAIN_CREATE_INFO_KHR);
if (swapchain_info && swapchain_info->swapchain != VK_NULL_HANDLE) {
return wsi_common_create_swapchain_image(
&pdev->wsi_device, pCreateInfo, swapchain_info->swapchain, pImage);
}
#endif
image = vk_zalloc2(&dev->vk.alloc, pAllocator, sizeof(*image), 8,
VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
if (!image)
return vk_error(dev, VK_ERROR_OUT_OF_HOST_MEMORY);
result = kk_image_init(dev, image, pCreateInfo);
if (result != VK_SUCCESS) {
vk_free2(&dev->vk.alloc, pAllocator, image);
return result;
}
*pImage = kk_image_to_handle(image);
return VK_SUCCESS;
}
VKAPI_ATTR void VKAPI_CALL
kk_DestroyImage(VkDevice device, VkImage _image,
const VkAllocationCallbacks *pAllocator)
{
VK_FROM_HANDLE(kk_device, dev, device);
VK_FROM_HANDLE(kk_image, image, _image);
if (!image)
return;
kk_image_finish(dev, image, pAllocator);
vk_free2(&dev->vk.alloc, pAllocator, image);
}
static void
kk_image_plane_add_req(struct kk_device *dev, const struct kk_image *image,
const struct kk_image_plane *plane, uint64_t *size_B,
uint32_t *align_B)
{
assert(util_is_power_of_two_or_zero64(*align_B));
uint64_t plane_size_B, plane_align_B;
kk_image_plane_size_align_B(dev, image, plane, &plane_size_B,
&plane_align_B);
*align_B = MAX2(*align_B, plane_align_B);
*size_B = align64(*size_B, plane_align_B);
*size_B += plane_size_B;
}
static void
kk_get_image_memory_requirements(struct kk_device *dev, struct kk_image *image,
VkImageAspectFlags aspects,
VkMemoryRequirements2 *pMemoryRequirements)
{
struct kk_physical_device *pdev = kk_device_physical(dev);
uint32_t memory_types = (1 << pdev->mem_type_count) - 1;
/* Remove non host visible heaps from the types for host image copy in case
* of potential issues. This should be removed when we get ReBAR.
*/
if (image->vk.usage & VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT) {
for (uint32_t i = 0; i < pdev->mem_type_count; i++) {
if (!(pdev->mem_types[i].propertyFlags &
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT))
memory_types &= ~BITFIELD_BIT(i);
}
}
// TODO hope for the best?
uint64_t size_B = 0;
uint32_t align_B = 0;
if (image->disjoint) {
uint8_t plane = kk_image_memory_aspects_to_plane(image, aspects);
kk_image_plane_add_req(dev, image, &image->planes[plane], &size_B,
&align_B);
} else {
for (unsigned plane = 0; plane < image->plane_count; plane++) {
kk_image_plane_add_req(dev, image, &image->planes[plane], &size_B,
&align_B);
}
}
if (image->stencil_copy_temp.layout.size_B > 0) {
kk_image_plane_add_req(dev, image, &image->stencil_copy_temp, &size_B,
&align_B);
}
pMemoryRequirements->memoryRequirements.memoryTypeBits = memory_types;
pMemoryRequirements->memoryRequirements.alignment = align_B;
pMemoryRequirements->memoryRequirements.size = size_B;
vk_foreach_struct_const(ext, pMemoryRequirements->pNext) {
switch (ext->sType) {
case VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS: {
VkMemoryDedicatedRequirements *dedicated = (void *)ext;
dedicated->prefersDedicatedAllocation =
image->vk.tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT;
dedicated->requiresDedicatedAllocation =
image->vk.tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT;
break;
}
default:
vk_debug_ignored_stype(ext->sType);
break;
}
}
}
VKAPI_ATTR void VKAPI_CALL
kk_GetImageMemoryRequirements2(VkDevice device,
const VkImageMemoryRequirementsInfo2 *pInfo,
VkMemoryRequirements2 *pMemoryRequirements)
{
VK_FROM_HANDLE(kk_device, dev, device);
VK_FROM_HANDLE(kk_image, image, pInfo->image);
const VkImagePlaneMemoryRequirementsInfo *plane_info =
vk_find_struct_const(pInfo->pNext, IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO);
const VkImageAspectFlags aspects =
image->disjoint ? plane_info->planeAspect : image->vk.aspects;
kk_get_image_memory_requirements(dev, image, aspects, pMemoryRequirements);
}
VKAPI_ATTR void VKAPI_CALL
kk_GetDeviceImageMemoryRequirements(VkDevice device,
const VkDeviceImageMemoryRequirements *pInfo,
VkMemoryRequirements2 *pMemoryRequirements)
{
VK_FROM_HANDLE(kk_device, dev, device);
ASSERTED VkResult result;
struct kk_image image = {0};
result = kk_image_init(dev, &image, pInfo->pCreateInfo);
assert(result == VK_SUCCESS);
const VkImageAspectFlags aspects =
image.disjoint ? pInfo->planeAspect : image.vk.aspects;
kk_get_image_memory_requirements(dev, &image, aspects, pMemoryRequirements);
kk_image_finish(dev, &image, NULL);
}
VKAPI_ATTR void VKAPI_CALL
kk_GetImageSparseMemoryRequirements2(
VkDevice device, const VkImageSparseMemoryRequirementsInfo2 *pInfo,
uint32_t *pSparseMemoryRequirementCount,
VkSparseImageMemoryRequirements2 *pSparseMemoryRequirements)
{
*pSparseMemoryRequirementCount = 0u;
}
VKAPI_ATTR void VKAPI_CALL
kk_GetDeviceImageSparseMemoryRequirements(
VkDevice device, const VkDeviceImageMemoryRequirements *pInfo,
uint32_t *pSparseMemoryRequirementCount,
VkSparseImageMemoryRequirements2 *pSparseMemoryRequirements)
{
*pSparseMemoryRequirementCount = 0u;
}
static void
kk_get_image_subresource_layout(struct kk_device *dev, struct kk_image *image,
const VkImageSubresource2KHR *pSubresource,
VkSubresourceLayout2KHR *pLayout)
{
const VkImageSubresource *isr = &pSubresource->imageSubresource;
const uint8_t p = kk_image_memory_aspects_to_plane(image, isr->aspectMask);
const struct kk_image_plane *plane = &image->planes[p];
uint64_t offset_B = 0;
if (!image->disjoint) {
uint32_t align_B = 0;
for (unsigned i = 0; i < p; i++) {
kk_image_plane_add_req(dev, image, &image->planes[i], &offset_B,
&align_B);
}
}
pLayout->subresourceLayout = (VkSubresourceLayout){
.offset = offset_B,
.size = plane->layout.size_B,
.rowPitch = plane->layout.linear_stride_B,
.arrayPitch = plane->layout.layer_stride_B,
.depthPitch = 1u,
};
}
VKAPI_ATTR void VKAPI_CALL
kk_GetImageSubresourceLayout2KHR(VkDevice device, VkImage _image,
const VkImageSubresource2KHR *pSubresource,
VkSubresourceLayout2KHR *pLayout)
{
VK_FROM_HANDLE(kk_device, dev, device);
VK_FROM_HANDLE(kk_image, image, _image);
kk_get_image_subresource_layout(dev, image, pSubresource, pLayout);
}
VKAPI_ATTR void VKAPI_CALL
kk_GetDeviceImageSubresourceLayoutKHR(
VkDevice device, const VkDeviceImageSubresourceInfoKHR *pInfo,
VkSubresourceLayout2KHR *pLayout)
{
VK_FROM_HANDLE(kk_device, dev, device);
ASSERTED VkResult result;
struct kk_image image = {0};
result = kk_image_init(dev, &image, pInfo->pCreateInfo);
assert(result == VK_SUCCESS);
kk_get_image_subresource_layout(dev, &image, pInfo->pSubresource, pLayout);
kk_image_finish(dev, &image, NULL);
}
static VkResult
kk_image_plane_bind(struct kk_device *dev, struct kk_image *image,
struct kk_image_plane *plane, struct kk_device_memory *mem,
uint64_t *offset_B)
{
uint64_t plane_size_B, plane_align_B;
kk_image_plane_size_align_B(dev, image, plane, &plane_size_B,
&plane_align_B);
*offset_B = align64(*offset_B, plane_align_B);
/* Linear textures in Metal need to be allocated through a buffer... */
if (plane->layout.optimized_layout)
plane->mtl_handle = mtl_new_texture_with_descriptor(
mem->bo->mtl_handle, &plane->layout, *offset_B);
else
plane->mtl_handle = mtl_new_texture_with_descriptor_linear(
mem->bo->map, &plane->layout, *offset_B);
plane->addr = mem->bo->gpu + *offset_B;
/* Create auxiliary 2D array texture for 3D images so we can use 2D views of
* it */
if (plane->layout.type == MTL_TEXTURE_TYPE_3D &&
(image->vk.create_flags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT)) {
struct kk_image_layout array_layout = plane->layout;
array_layout.type = MTL_TEXTURE_TYPE_2D_ARRAY;
// TODO_KOSMICKRISP We need to make sure that this doesn't go over Metal's
// layer maximum which is 2048. Probably by limiting the dimensions and
// layers for 3D images
array_layout.layers = array_layout.layers * array_layout.depth_px;
array_layout.depth_px = 1u;
plane->mtl_handle_array = mtl_new_texture_with_descriptor(
mem->bo->mtl_handle, &array_layout, *offset_B);
}
*offset_B += plane_size_B;
return VK_SUCCESS;
}
static VkResult
kk_bind_image_memory(struct kk_device *dev, const VkBindImageMemoryInfo *info)
{
VK_FROM_HANDLE(kk_device_memory, mem, info->memory);
VK_FROM_HANDLE(kk_image, image, info->image);
VkResult result;
/* Ignore this struct on Android, we cannot access swapchain structures
* there. */
#ifdef KK_USE_WSI_PLATFORM
const VkBindImageMemorySwapchainInfoKHR *swapchain_info =
vk_find_struct_const(info->pNext, BIND_IMAGE_MEMORY_SWAPCHAIN_INFO_KHR);
if (swapchain_info && swapchain_info->swapchain != VK_NULL_HANDLE) {
VK_FROM_HANDLE(wsi_swapchain, swapchain, swapchain_info->swapchain);
VkImage _wsi_image =
swapchain->get_wsi_image(swapchain, swapchain_info->imageIndex)->image;
VK_FROM_HANDLE(kk_image, wsi_img, _wsi_image);
assert(image->plane_count == 1);
assert(wsi_img->plane_count == 1);
struct kk_image_plane *plane = &image->planes[0];
struct kk_image_plane *swapchain_plane = &wsi_img->planes[0];
/* Copy swapchain plane data retaining relevant resources. */
plane->layout = swapchain_plane->layout;
plane->mtl_handle = mtl_retain(swapchain_plane->mtl_handle);
plane->mtl_handle_array =
swapchain_plane->mtl_handle_array
? mtl_retain(swapchain_plane->mtl_handle_array)
: NULL;
plane->addr = swapchain_plane->addr;
return VK_SUCCESS;
}
#endif
uint64_t offset_B = info->memoryOffset;
if (image->disjoint) {
const VkBindImagePlaneMemoryInfo *plane_info =
vk_find_struct_const(info->pNext, BIND_IMAGE_PLANE_MEMORY_INFO);
const uint8_t plane =
kk_image_memory_aspects_to_plane(image, plane_info->planeAspect);
result =
kk_image_plane_bind(dev, image, &image->planes[plane], mem, &offset_B);
if (result != VK_SUCCESS)
return result;
} else {
for (unsigned plane = 0; plane < image->plane_count; plane++) {
result = kk_image_plane_bind(dev, image, &image->planes[plane], mem,
&offset_B);
if (result != VK_SUCCESS)
return result;
}
}
if (image->stencil_copy_temp.layout.size_B > 0) {
result = kk_image_plane_bind(dev, image, &image->stencil_copy_temp, mem,
&offset_B);
if (result != VK_SUCCESS)
return result;
}
return VK_SUCCESS;
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_BindImageMemory2(VkDevice device, uint32_t bindInfoCount,
const VkBindImageMemoryInfo *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_image_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 = VK_SUCCESS;
if (first_error_or_success == VK_SUCCESS)
first_error_or_success = result;
}
return first_error_or_success;
}
VKAPI_ATTR VkResult VKAPI_CALL
kk_GetImageOpaqueCaptureDescriptorDataEXT(
VkDevice _device, const VkImageCaptureDescriptorDataInfoEXT *pInfo,
void *pData)
{
return VK_SUCCESS;
}

View file

@ -0,0 +1,155 @@
/*
* Copyright © 2022 Collabora Ltd. and Red Hat Inc.
* Copyright 2025 LunarG, Inc.
* Copyright 2025 Google LLC
* SPDX-License-Identifier: MIT
*/
#ifndef KK_IMAGE_H
#define KK_IMAGE_H 1
#include "kk_private.h"
#include "kk_device_memory.h"
#include "kk_image_layout.h"
#include "kosmickrisp/bridge/mtl_types.h"
#include "vk_image.h"
/* Because small images can end up with an array_stride_B that is less than
* the sparse block size (in bytes), we have to set SINGLE_MIPTAIL_BIT when
* advertising sparse properties to the client. This means that we get one
* single memory range for the miptail of the image. For large images with
* mipTailStartLod > 0, we have to deal with the array stride ourselves.
*
* We do this by returning NVK_MIP_TAIL_START_OFFSET as the image's
* imageMipTailOffset. We can then detect anything with that address as
* being part of the miptail and re-map it accordingly. The Vulkan spec
* explicitly allows for this.
*
* From the Vulkan 1.3.279 spec:
*
* "When VK_SPARSE_MEMORY_BIND_METADATA_BIT is present, the resourceOffset
* must have been derived explicitly from the imageMipTailOffset in the
* sparse resource properties returned for the metadata aspect. By
* manipulating the value returned for imageMipTailOffset, the
* resourceOffset does not have to correlate directly to a device virtual
* address offset, and may instead be whatever value makes it easiest for
* the implementation to derive the correct device virtual address."
*/
#define NVK_MIP_TAIL_START_OFFSET 0x6d74000000000000UL
struct kk_device_memory;
struct kk_physical_device;
struct kk_queue;
VkFormatFeatureFlags2
kk_get_image_format_features(struct kk_physical_device *pdevice,
VkFormat format, VkImageTiling tiling,
uint64_t drm_format_mod);
uint32_t kk_image_max_dimension(VkImageType image_type);
struct kk_image_plane {
struct kk_image_layout layout;
// TODO_KOSMICKRISP Only have one handle since we will only create 2D arrays
// anyway
/* Metal handle with original handle type */
mtl_texture *mtl_handle;
/* Metal handle with 2D array type for 3D images */
mtl_texture *mtl_handle_array;
uint64_t addr;
};
struct kk_image {
struct vk_image vk;
/** True if the planes are bound separately
* * This is set based on VK_IMAGE_CREATE_DISJOINT_BIT
*/
bool disjoint;
uint8_t plane_count;
struct kk_image_plane planes[3];
/* In order to support D32_SFLOAT_S8_UINT, a temp area is
* needed. The stencil plane can't be a copied using the DMA
* engine in a single pass since it would need 8 components support.
* Instead we allocate a 16-bit temp, that gets copied into, then
* copied again down to the 8-bit result.
*/
struct kk_image_plane stencil_copy_temp;
};
static inline mtl_resource *
kk_image_to_mtl_resource(const struct kk_image *image, int plane)
{
if (image != NULL) {
assert(plane < ARRAY_SIZE(image->planes));
return (mtl_resource *)image->planes[plane].mtl_handle;
}
return NULL;
}
VK_DEFINE_NONDISP_HANDLE_CASTS(kk_image, vk.base, VkImage, VK_OBJECT_TYPE_IMAGE)
static inline uint64_t
kk_image_plane_base_address(const struct kk_image_plane *plane)
{
return plane->addr;
}
static inline uint64_t
kk_image_base_address(const struct kk_image *image, uint8_t plane)
{
return kk_image_plane_base_address(&image->planes[plane]);
}
static inline uint8_t
kk_image_aspects_to_plane(ASSERTED const struct kk_image *image,
VkImageAspectFlags aspectMask)
{
/* Memory planes are only allowed for memory operations */
assert(!(aspectMask & (VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT |
VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT |
VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT |
VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT)));
/* Verify that the aspects are actually in the image */
assert(!(aspectMask & ~image->vk.aspects));
/* Must only be one aspect unless it's depth/stencil */
assert(aspectMask ==
(VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT) ||
util_bitcount(aspectMask) == 1);
switch (aspectMask) {
case VK_IMAGE_ASPECT_PLANE_1_BIT:
return 1;
case VK_IMAGE_ASPECT_PLANE_2_BIT:
return 2;
default:
return 0;
}
}
static inline uint8_t
kk_image_memory_aspects_to_plane(ASSERTED const struct kk_image *image,
VkImageAspectFlags aspectMask)
{
if (aspectMask & (VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT |
VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT |
VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT |
VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT)) {
/* We don't support DRM format modifiers on anything but single-plane
* color at the moment.
*/
assert(aspectMask == VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT);
return 0;
} else {
return kk_image_aspects_to_plane(image, aspectMask);
}
}
#endif

Some files were not shown because too many files have changed in this diff Show more