Linked repository source
bindings/typed/render/rendering.odin
This read-only view comes from the file in the Gododin repository. It retains its design, example or evidence context.
Download the original file · Return to the guide
package render
// Generated by Gododin. Do not edit.
import "core:mem"
import "core:sync"
import raw "../../raw"
import godot "../godot"
BOUND_API_SCHEMA_VERSION :: 4
MAX_UNIFORM_BINDINGS :: 8
Call_Role :: enum u8 {
Invalid,
Main,
Render,
}
Call_Error :: enum u8 {
None,
Stale_Generation,
Wrong_Role,
Missing_API,
Invalid_Handle,
Invalid_Owner,
Wrong_Device,
Wrong_RID_Kind,
Out_Of_Range,
Invalid_State,
Godot_Error,
Allocation_Failed,
Synchronization_Required,
}
Device_Kind :: enum u8 {
Invalid,
Global,
Local,
}
RID_Kind :: enum u8 {
Invalid,
Shader,
Storage_Buffer,
Uniform_Set,
Compute_Pipeline,
Texture_Borrow,
}
Uniform_Type :: enum i64 {
Image = 3,
Storage_Buffer = 8,
}
Rendering_Server_Borrow :: distinct raw.GDExtensionObjectPtr
Rendering_Device_Object :: distinct raw.GDExtensionObjectPtr
Render_Data_Borrow :: distinct raw.GDExtensionObjectPtr
Render_Scene_Buffers_Borrow :: distinct raw.GDExtensionObjectPtr
Render_Scene_Buffers_RD_Borrow :: distinct raw.GDExtensionObjectPtr
RD_Shader_SPIRV_Borrow :: distinct raw.GDExtensionObjectPtr
RD_Shader_Source_Borrow :: distinct raw.GDExtensionObjectPtr
Owned_Ref :: struct {
value: raw.GDExtensionObjectPtr,
live: bool,
}
Owned_Local_Device :: struct {
object: raw.GDExtensionObjectPtr,
identity: uintptr,
live: bool,
outstanding_rids: u32,
work_recorded: bool,
submitted: bool,
synchronized: bool,
}
Device_Borrow :: struct {
object: raw.GDExtensionObjectPtr,
kind: Device_Kind,
identity: uintptr,
creator: Call_Role,
local_owner: ^Owned_Local_Device,
}
Owned_RID :: struct {
value: raw.RID,
kind: RID_Kind,
device_kind: Device_Kind,
device: uintptr,
producer_id: u64,
creator_role: Call_Role,
local_owner: ^Owned_Local_Device,
live: bool,
}
Borrowed_RID :: struct {
value: raw.RID,
kind: RID_Kind,
device_kind: Device_Kind,
device: uintptr,
producer_id: u64,
creator_role: Call_Role,
local_owner: ^Owned_Local_Device,
}
Compute_List :: struct {
value: i64,
device_kind: Device_Kind,
device: uintptr,
creator_role: Call_Role,
local_owner: ^Owned_Local_Device,
open: bool,
}
Packed_Byte_Array :: struct {
value: raw.PackedByteArray,
live: bool,
}
Packed_Float32_Array :: struct {
value: raw.PackedFloat32Array,
live: bool,
}
Vector2i :: struct {
x: i32,
y: i32,
}
Uniform_Binding :: struct {
type: Uniform_Type,
binding: u32,
rid: Borrowed_RID,
}
Bound_API :: struct {
schema_version: u32,
size: uintptr,
generation: u64,
published: bool,
// The complete generated class-method projection is the only operation
// surface. This authored package retains the helper interfaces needed by its
// multi-call owners and reviewed provenance narrowings.
typed: godot.Bound_Interface,
object_destroy: raw.raw_godot_interface_object_destroy,
packed_byte_array_index: raw.raw_godot_interface_packed_byte_array_operator_index,
packed_byte_array_index_const: raw.raw_godot_interface_packed_byte_array_operator_index_const,
string_new_utf8: raw.raw_godot_interface_string_new_with_utf8_chars_and_len2,
variant_destroy: raw.raw_godot_interface_variant_destroy,
variant_from_object: raw.GDExtensionVariantFromTypeConstructorFunc,
array_constructor: raw.GDExtensionPtrConstructor,
array_destructor: raw.GDExtensionPtrDestructor,
array_append: raw.GDExtensionPtrBuiltInMethod,
packed_byte_array_constructor: raw.GDExtensionPtrConstructor,
packed_byte_array_destructor: raw.GDExtensionPtrDestructor,
packed_byte_array_resize: raw.GDExtensionPtrBuiltInMethod,
packed_byte_array_size: raw.GDExtensionPtrBuiltInMethod,
packed_float32_array_index: raw.raw_godot_interface_packed_float32_array_operator_index,
packed_float32_array_index_const: raw.raw_godot_interface_packed_float32_array_operator_index_const,
packed_float32_array_constructor: raw.GDExtensionPtrConstructor,
packed_float32_array_destructor: raw.GDExtensionPtrDestructor,
packed_float32_array_resize: raw.GDExtensionPtrBuiltInMethod,
packed_float32_array_size: raw.GDExtensionPtrBuiltInMethod,
rid_get_id: raw.GDExtensionPtrBuiltInMethod,
rid_is_valid: raw.GDExtensionPtrBuiltInMethod,
string_constructor: raw.GDExtensionPtrConstructor,
string_destructor: raw.GDExtensionPtrDestructor,
}
Call :: struct {
api: ^Bound_API,
generation: u64,
role: Call_Role,
}
bound_api_is_complete :: proc(api: ^Bound_API) -> bool {
if api == nil || api.schema_version != BOUND_API_SCHEMA_VERSION ||
api.size != uintptr(size_of(Bound_API)) || api.generation == 0 || !api.published ||
api.typed.generation != api.generation || api.typed.published != api.published ||
!godot.bound_interface_is_complete(&api.typed) || api.object_destroy == nil ||
api.packed_byte_array_index == nil ||
api.packed_byte_array_index_const == nil || api.string_new_utf8 == nil ||
api.variant_destroy == nil ||
api.variant_from_object == nil || api.array_constructor == nil ||
api.array_destructor == nil || api.array_append == nil ||
api.packed_byte_array_constructor == nil || api.packed_byte_array_destructor == nil ||
api.packed_byte_array_resize == nil || api.packed_byte_array_size == nil ||
api.packed_float32_array_index == nil || api.packed_float32_array_index_const == nil ||
api.packed_float32_array_constructor == nil || api.packed_float32_array_destructor == nil ||
api.packed_float32_array_resize == nil || api.packed_float32_array_size == nil ||
api.rid_get_id == nil || api.rid_is_valid == nil ||
api.string_constructor == nil || api.string_destructor == nil {
return false
}
return true
}
main_call :: proc(api: ^Bound_API) -> Call {
return {api=api, generation=api.generation if api != nil else 0, role=.Main}
}
render_call :: proc(api: ^Bound_API) -> Call {
return {api=api, generation=api.generation if api != nil else 0, role=.Render}
}
validate_call :: proc(call: Call) -> Call_Error {
if call.api == nil || !call.api.published ||
call.api.schema_version != BOUND_API_SCHEMA_VERSION ||
call.api.size != uintptr(size_of(Bound_API)) ||
call.generation == 0 || call.generation != call.api.generation {
return .Stale_Generation
}
if call.role != .Main && call.role != .Render do return .Wrong_Role
return .None
}
generated_role :: proc(role: Call_Role) -> godot.Call_Role {
switch role {
case .Main: return .Main
case .Render: return .Render
case .Invalid: return .Invalid
}
return .Invalid
}
generated_call :: proc(call: Call) -> godot.Call {
return {api=&call.api.typed if call.api != nil else nil, generation=call.generation, role=generated_role(call.role)}
}
generated_error :: proc(error: godot.Call_Error) -> Call_Error {
switch error {
case .None: return .None
case .Stale_Generation: return .Stale_Generation
case .Wrong_Role: return .Wrong_Role
case .Missing_Interface: return .Missing_API
case .Unbound_Operation: return .Missing_API
case .Invalid_Handle: return .Invalid_Handle
case .Invalid_Owner: return .Invalid_Owner
case .Too_Many_Arguments: return .Out_Of_Range
case .Godot_Call_Failed: return .Godot_Error
}
return .Godot_Error
}
const_type_pointer :: proc(value: rawptr) -> raw.GDExtensionConstTypePtr {
return raw.GDExtensionConstTypePtr(value)
}
type_pointer :: proc(value: rawptr) -> raw.GDExtensionTypePtr {
return raw.GDExtensionTypePtr(value)
}
next_producer_id :: proc(api: ^Bound_API) -> u64 {
return sync.atomic_add(&api.typed.producer_sequence, u64(1)) + 1
}
// rid_valid is the engine's own RID.is_valid(): a non-zero id check, not a
// liveness probe. A resource the scene has freed keeps its non-zero RID, so
// scene-side invalidation is not observable here — the server itself rejects
// writes to such a RID with its own diagnostic, and rebinding through close
// plus open is the recovery. Scene RID liveness stays a caller constraint,
// like the staged store's retention constraints.
rid_valid :: proc(api: ^Bound_API, value: raw.RID) -> bool {
if api == nil do return false
result := raw.GDExtensionBool(0)
local := value
api.rid_is_valid(type_pointer(rawptr(&local)), nil, type_pointer(rawptr(&result)), 0)
return result != 0
}
rid_identity :: proc(api: ^Bound_API, value: Borrowed_RID) -> (u64, Call_Error) {
if api == nil || value.kind == .Invalid || !rid_valid(api, value.value) do return 0, .Invalid_Handle
local := value.value
result: i64
api.rid_get_id(type_pointer(rawptr(&local)), nil, type_pointer(rawptr(&result)), 0)
if result <= 0 do return 0, .Invalid_Handle
return u64(result), .None
}
device_valid :: proc(value: Device_Borrow) -> bool {
if rawptr(value.object) == nil || value.identity != uintptr(rawptr(value.object)) ||
(value.creator != .Main && value.creator != .Render) {
return false
}
if value.kind == .Global do return value.creator == .Render && value.local_owner == nil
if value.kind == .Local do return value.creator == .Main && value.local_owner != nil &&
value.local_owner.live && value.local_owner.object == value.object && value.local_owner.identity == value.identity
return false
}
device_call_valid :: proc(call: Call, device: Device_Borrow) -> Call_Error {
if error := validate_call(call); error != .None do return error
if !device_valid(device) do return .Invalid_Handle
if call.role != device.creator do return .Wrong_Role
return .None
}
owned_rid_borrow :: proc(owner: ^Owned_RID) -> (Borrowed_RID, bool) {
if owner == nil || !owner.live do return {}, false
return {
value=owner.value,
kind=owner.kind,
device_kind=owner.device_kind,
device=owner.device,
producer_id=owner.producer_id,
creator_role=owner.creator_role,
local_owner=owner.local_owner,
}, true
}
rid_matches_device :: proc(api: ^Bound_API, value: Borrowed_RID, device: Device_Borrow) -> bool {
return value.kind != .Invalid && value.device_kind == device.kind && value.device == device.identity &&
value.creator_role == device.creator && value.local_owner == device.local_owner && rid_valid(api, value.value)
}
new_owned_rid :: proc(
call: Call,
device: Device_Borrow,
kind: RID_Kind,
generated: godot.Resource_Handle,
) -> (Owned_RID, Call_Error) {
if !generated.live || generated.producer != device.object ||
generated.producer_role != generated_role(call.role) ||
!rid_valid(call.api, generated.value) {
return {}, .Invalid_Handle
}
if device.kind == .Local {
if device.local_owner.outstanding_rids == max(u32) do return {}, .Out_Of_Range
device.local_owner.outstanding_rids += 1
}
return {
value=generated.value,
kind=kind,
device_kind=device.kind,
device=device.identity,
producer_id=generated.producer_sequence,
creator_role=call.role,
local_owner=device.local_owner,
live=true,
}, .None
}
rendering_server :: proc(call: Call) -> (Rendering_Server_Borrow, Call_Error) {
if error := validate_call(call); error != .None do return {}, error
server, generated_failure := godot.singleton_rendering_server(generated_call(call))
if generated_failure != .None do return {}, generated_error(generated_failure)
if rawptr(server.ptr) == nil do return {}, .Invalid_Handle
return Rendering_Server_Borrow(server.ptr), .None
}
rendering_server_get_global_device :: proc(
call: Call,
server: Rendering_Server_Borrow,
) -> (Device_Borrow, Call_Error) {
if error := validate_call(call); error != .None do return {}, error
if call.role != .Render do return {}, .Wrong_Role
if rawptr(server) == nil do return {}, .Invalid_Handle
server_handle: godot.RenderingServer
server_handle.ptr = raw.GDExtensionObjectPtr(server)
result, generated_failure := godot.rendering_server_get_rendering_device(generated_call(call), &server_handle)
if generated_failure != .None do return {}, generated_error(generated_failure)
if rawptr(result.ptr) == nil do return {}, .Invalid_Handle
return {object=result.ptr, kind=.Global, identity=uintptr(rawptr(result.ptr)), creator=.Render}, .None
}
rendering_server_create_local_device :: proc(
call: Call,
server: Rendering_Server_Borrow,
) -> (Owned_Local_Device, Call_Error) {
if error := validate_call(call); error != .None do return {}, error
if call.role != .Main do return {}, .Wrong_Role
if rawptr(server) == nil do return {}, .Invalid_Handle
server_handle: godot.RenderingServer
server_handle.ptr = raw.GDExtensionObjectPtr(server)
result, generated_failure := godot.rendering_server_create_local_rendering_device(generated_call(call), &server_handle)
if generated_failure != .None do return {}, generated_error(generated_failure)
if rawptr(result.ptr) == nil do return {}, .Invalid_Handle
return {object=result.ptr, identity=uintptr(rawptr(result.ptr)), live=true}, .None
}
owned_local_device_borrow :: proc(owner: ^Owned_Local_Device) -> (Device_Borrow, bool) {
if owner == nil || !owner.live || owner.object == nil || owner.identity != uintptr(rawptr(owner.object)) do return {}, false
return {object=owner.object, kind=.Local, identity=owner.identity, creator=.Main, local_owner=owner}, true
}
owned_local_device_destroy :: proc(call: Call, owner: ^Owned_Local_Device) -> Call_Error {
if error := validate_call(call); error != .None do return error
if call.role != .Main do return .Wrong_Role
if owner == nil || !owner.live || owner.object == nil do return .Invalid_Owner
if owner.outstanding_rids != 0 do return .Invalid_State
if owner.work_recorded && !owner.synchronized do return .Synchronization_Required
call.api.object_destroy(owner.object)
owner^ = {}
return .None
}
ref_counted_release :: proc(call: Call, owner: ^Owned_Ref) -> Call_Error {
if error := validate_call(call); error != .None do return error
if owner == nil || !owner.live || owner.value == nil do return .Invalid_Owner
object := owner.value
handle: godot.RefCounted
handle.ptr = object
result, generated_failure := godot.ref_counted_unreference(generated_call(call), &handle)
if generated_failure != .None do return generated_error(generated_failure)
owner^ = {}
if result do call.api.object_destroy(object)
return .None
}
render_data_get_scene_buffers :: proc(
call: Call,
render_data: Render_Data_Borrow,
) -> (Owned_Ref, Call_Error) {
if error := validate_call(call); error != .None do return {}, error
if call.role != .Render do return {}, .Wrong_Role
if rawptr(render_data) == nil do return {}, .Invalid_Handle
handle: godot.RenderData
handle.ptr = raw.GDExtensionObjectPtr(render_data)
result, generated_failure := godot.render_data_get_render_scene_buffers(generated_call(call), &handle)
if generated_failure != .None do return {}, generated_error(generated_failure)
if !result.live || rawptr(result.handle.ptr) == nil do return {}, .Invalid_Handle
return {value=result.handle.ptr, live=true}, .None
}
owned_ref_borrow_render_scene_buffers :: proc(owner: ^Owned_Ref) -> (Render_Scene_Buffers_Borrow, bool) {
if owner == nil || !owner.live || owner.value == nil do return {}, false
return Render_Scene_Buffers_Borrow(owner.value), true
}
// Exact Forward+ source guarantees RenderData's owned RenderSceneBuffers
// reference points to RenderSceneBuffersRD. The borrow remains bounded by owner.
render_scene_buffers_rd_from_forward_plus :: proc(
value: Render_Scene_Buffers_Borrow,
) -> (Render_Scene_Buffers_RD_Borrow, bool) {
if rawptr(value) == nil do return {}, false
return Render_Scene_Buffers_RD_Borrow(value), true
}
render_scene_buffers_get_color_layer :: proc(
call: Call,
device: Device_Borrow,
buffers: Render_Scene_Buffers_RD_Borrow,
layer: u32,
msaa: bool,
) -> (Borrowed_RID, Call_Error) {
if error := device_call_valid(call, device); error != .None do return {}, error
if device.kind != .Global || rawptr(buffers) == nil do return {}, .Wrong_Device
handle: godot.RenderSceneBuffersRD
handle.ptr = raw.GDExtensionObjectPtr(buffers)
result, generated_failure := godot.render_scene_buffers_rd_get_color_layer(generated_call(call), &handle, layer, msaa)
if generated_failure != .None do return {}, generated_error(generated_failure)
if !result.live || result.producer != handle.ptr ||
result.producer_role != generated_role(call.role) || !rid_valid(call.api, result.value) {
return {}, .Invalid_Handle
}
return {
value=result.value,
kind=.Texture_Borrow,
device_kind=.Global,
device=device.identity,
producer_id=result.producer_sequence,
creator_role=.Render,
}, .None
}
render_scene_buffers_get_internal_size :: proc(
call: Call,
buffers: Render_Scene_Buffers_RD_Borrow,
) -> (Vector2i, Call_Error) {
if error := validate_call(call); error != .None do return {}, error
if call.role != .Render do return {}, .Wrong_Role
if rawptr(buffers) == nil do return {}, .Invalid_Handle
handle: godot.RenderSceneBuffersRD
handle.ptr = raw.GDExtensionObjectPtr(buffers)
result, generated_failure := godot.render_scene_buffers_rd_get_internal_size(generated_call(call), &handle)
if generated_failure != .None do return {}, generated_error(generated_failure)
return transmute(Vector2i)result, .None
}
render_scene_buffers_get_view_count :: proc(
call: Call,
buffers: Render_Scene_Buffers_RD_Borrow,
) -> (u32, Call_Error) {
if error := validate_call(call); error != .None do return 0, error
if call.role != .Render do return 0, .Wrong_Role
if rawptr(buffers) == nil do return 0, .Invalid_Handle
handle: godot.RenderSceneBuffersRD
handle.ptr = raw.GDExtensionObjectPtr(buffers)
result, generated_failure := godot.render_scene_buffers_rd_get_view_count(generated_call(call), &handle)
if generated_failure != .None do return 0, generated_error(generated_failure)
if result == 0 do return 0, .Out_Of_Range
return result, .None
}
packed_byte_array_from_bytes :: proc(api: ^Bound_API, bytes: []u8) -> (Packed_Byte_Array, Call_Error) {
if api == nil || len(bytes) > int(max(u32)) do return {}, .Out_Of_Range
result := Packed_Byte_Array{live=true}
api.packed_byte_array_constructor(
raw.GDExtensionUninitializedTypePtr(rawptr(&result.value)),
nil,
)
if len(bytes) == 0 do return result, .None
size := i64(len(bytes))
arguments := [1]raw.GDExtensionConstTypePtr{const_type_pointer(rawptr(&size))}
resize_result: i64
api.packed_byte_array_resize(
type_pointer(rawptr(&result.value)),
&arguments[0],
type_pointer(rawptr(&resize_result)),
1,
)
if resize_result != 0 {
api.packed_byte_array_destructor(type_pointer(rawptr(&result.value)))
return {}, .Allocation_Failed
}
// One acquisition, one copy. The mutable accessor detaches copy-on-write
// before it returns a pointer, so the write path may use it; the bound comes
// from the engine's own size rather than from a null return, because a bulk
// move never probes an out-of-range index.
base, bound, bound_error := packed_byte_array_write_base(api, &result)
if bound_error != .None || bound != len(bytes) {
api.packed_byte_array_destructor(type_pointer(rawptr(&result.value)))
return {}, .Invalid_State if bound_error == .None else bound_error
}
mem.copy_non_overlapping(rawptr(base), raw_data(bytes), len(bytes))
return result, .None
}
// packed_byte_array_length reads the engine's own size for a packed array. A
// bulk move needs an established bound: the per-element loops this replaced
// derived one from the operator index returning null out of range, which a
// pointer-and-length move does not do.
packed_byte_array_length :: proc(api: ^Bound_API, owner: ^Packed_Byte_Array) -> (int, Call_Error) {
if api == nil do return 0, .Missing_API
if owner == nil || !owner.live do return 0, .Invalid_Owner
result: i64
api.packed_byte_array_size(
type_pointer(rawptr(&owner.value)),
nil,
type_pointer(rawptr(&result)),
0,
)
if result < 0 || result > i64(max(int)) do return 0, .Out_Of_Range
return int(result), .None
}
// packed_byte_array_write_base acquires the writable backing store once. The
// mutable accessor detaches copy-on-write, so acquisition itself is safe; the
// hazard this facade guards is post-acquisition aliasing, handled structurally
// by Packed_Byte_Staging.
packed_byte_array_write_base :: proc(api: ^Bound_API, owner: ^Packed_Byte_Array) -> (^u8, int, Call_Error) {
bound, bound_error := packed_byte_array_length(api, owner)
if bound_error != .None do return nil, 0, bound_error
if bound == 0 do return nil, 0, .None
base := api.packed_byte_array_index(type_pointer(rawptr(&owner.value)), 0)
if base == nil do return nil, 0, .Invalid_State
return base, bound, .None
}
// packed_byte_array_read_base acquires the read-only backing store once. It must
// use the const accessor: the mutable one detaches copy-on-write and would force
// exactly the copy this path exists to avoid.
packed_byte_array_read_base :: proc(api: ^Bound_API, owner: ^Packed_Byte_Array) -> (^u8, int, Call_Error) {
bound, bound_error := packed_byte_array_length(api, owner)
if bound_error != .None do return nil, 0, bound_error
if bound == 0 do return nil, 0, .None
base := api.packed_byte_array_index_const(const_type_pointer(rawptr(&owner.value)), 0)
if base == nil do return nil, 0, .Invalid_State
return base, bound, .None
}
// Packed_Byte_Staging retains one packed array for the process lifetime of a
// transfer so a pure Odin subsystem writes into the engine's own backing store
// and the engine performs the only copy. It removes the per-frame allocation and
// the per-element foreign call from the bulk path.
//
// Sole ownership is structural, not a uniqueness test. GDExtension exposes no
// reference count or uniqueness query for a copy-on-write packed array, so an
// invariant written as "invalidate on losing uniqueness" would silently do
// nothing. Instead: the owner never copies the array value, never hands it
// anywhere the engine can retain, re-acquires the base pointer every frame and
// treats a changed pointer as detachment, and invalidates on a changed size.
// Acquisition itself is safe because the mutable accessor detaches first; the
// hazard is entirely post-acquisition aliasing.
//
// The owner declares its thread roles. It is retained in image state, never in a
// Render callback, which gets no retained Host state.
Packed_Byte_Staging :: struct {
array: Packed_Byte_Array,
base: ^u8,
length: int,
publisher_role: Call_Role,
consumer_role: Call_Role,
generation: u64,
published: u64,
consumed: u64,
live: bool,
}
packed_byte_staging_create :: proc(
api: ^Bound_API,
length: int,
publisher_role: Call_Role,
consumer_role: Call_Role,
) -> (Packed_Byte_Staging, Call_Error) {
if !bound_api_is_complete(api) do return {}, .Missing_API
if length <= 0 || length > int(max(u32)) do return {}, .Out_Of_Range
if publisher_role == .Invalid || consumer_role == .Invalid do return {}, .Wrong_Role
array, array_error := packed_byte_array_zeroed(api, length)
if array_error != .None do return {}, array_error
result := Packed_Byte_Staging{
array=array,
length=length,
publisher_role=publisher_role,
consumer_role=consumer_role,
generation=api.generation,
live=true,
}
base, bound, base_error := packed_byte_array_write_base(api, &result.array)
if base_error != .None || bound != length {
packed_byte_array_destroy(api, &result.array)
return {}, .Invalid_State if base_error == .None else base_error
}
result.base = base
return result, .None
}
// packed_byte_staging_write returns the writable backing store to the declared
// publisher role. It re-acquires the base pointer and re-reads the size on every
// call, so a detached or resized array fails here rather than corrupting a frame.
packed_byte_staging_write :: proc(
api: ^Bound_API,
staging: ^Packed_Byte_Staging,
role: Call_Role,
) -> ([]u8, Call_Error) {
if api == nil do return nil, .Missing_API
if staging == nil || !staging.live || !staging.array.live do return nil, .Invalid_Owner
if role != staging.publisher_role do return nil, .Wrong_Role
if api.generation != staging.generation do return nil, .Stale_Generation
base, bound, base_error := packed_byte_array_write_base(api, &staging.array)
if base_error != .None {
staging.live = false
return nil, base_error
}
if base != staging.base || bound != staging.length {
staging.live = false
return nil, .Invalid_State
}
return mem.slice_ptr(base, bound), .None
}
// packed_byte_staging_publish orders the publisher's writes before the consumer
// observes them. Main publishes, Render consumes, and the release/acquire pair
// is what makes the shared backing store a protocol rather than an
// unsynchronized cross-role alias.
packed_byte_staging_publish :: proc(staging: ^Packed_Byte_Staging, role: Call_Role) -> (u64, Call_Error) {
if staging == nil || !staging.live do return 0, .Invalid_Owner
if role != staging.publisher_role do return 0, .Wrong_Role
next := staging.published + 1
sync.atomic_store_explicit(&staging.published, next, .Release)
return next, .None
}
packed_byte_staging_pending :: proc(staging: ^Packed_Byte_Staging, role: Call_Role) -> (u64, bool) {
if staging == nil || !staging.live do return 0, false
if role != staging.consumer_role do return 0, false
published := sync.atomic_load_explicit(&staging.published, .Acquire)
return published, published != staging.consumed
}
packed_byte_staging_destroy :: proc(api: ^Bound_API, staging: ^Packed_Byte_Staging) -> Call_Error {
if api == nil do return .Missing_API
if staging == nil || !staging.live && !staging.array.live do return .Invalid_Owner
error := Call_Error.None
if staging.array.live do error = packed_byte_array_destroy(api, &staging.array)
staging^ = {}
return error
}
// packed_byte_array_zeroed builds an engine-owned array of the requested length
// without a caller-side source buffer, so a staging owner never allocates a
// second copy just to initialize.
packed_byte_array_zeroed :: proc(api: ^Bound_API, length: int) -> (Packed_Byte_Array, Call_Error) {
if api == nil || length < 0 || length > int(max(u32)) do return {}, .Out_Of_Range
result := Packed_Byte_Array{live=true}
api.packed_byte_array_constructor(
raw.GDExtensionUninitializedTypePtr(rawptr(&result.value)),
nil,
)
if length == 0 do return result, .None
size := i64(length)
arguments := [1]raw.GDExtensionConstTypePtr{const_type_pointer(rawptr(&size))}
resize_result: i64
api.packed_byte_array_resize(
type_pointer(rawptr(&result.value)),
&arguments[0],
type_pointer(rawptr(&resize_result)),
1,
)
if resize_result != 0 {
api.packed_byte_array_destructor(type_pointer(rawptr(&result.value)))
return {}, .Allocation_Failed
}
return result, .None
}
packed_byte_array_destroy :: proc(api: ^Bound_API, owner: ^Packed_Byte_Array) -> Call_Error {
if api == nil do return .Missing_API
if owner == nil || !owner.live do return .Invalid_Owner
api.packed_byte_array_destructor(type_pointer(rawptr(&owner.value)))
owner^ = {}
return .None
}
packed_byte_array_read_exact :: proc(api: ^Bound_API, owner: ^Packed_Byte_Array, output: []u8) -> Call_Error {
if api == nil do return .Missing_API
if owner == nil || !owner.live do return .Invalid_Owner
if len(output) == 0 do return .None
base, bound, bound_error := packed_byte_array_read_base(api, owner)
if bound_error != .None do return bound_error
if base == nil || bound < len(output) do return .Out_Of_Range
mem.copy_non_overlapping(raw_data(output), rawptr(base), len(output))
return .None
}
// The packed-float32 kit mirrors the packed-byte kit primitive for primitive.
// A PackedFloat32Array's engine size counts f32 elements, so every length below
// is an element count; the engine stores four bytes per element. The MultiMesh
// TRANSFORM_3D buffer is exactly this element domain — twelve floats per
// instance — which is why the staging owner is typed rather than a byte view.
//
// packed_float32_array_zeroed builds an engine-owned array of the requested
// element count without a caller-side source buffer, so a staging owner never
// allocates a second copy just to initialize.
packed_float32_array_zeroed :: proc(api: ^Bound_API, length: int) -> (Packed_Float32_Array, Call_Error) {
if api == nil || length < 0 || length > int(max(u32)) do return {}, .Out_Of_Range
result := Packed_Float32_Array{live=true}
api.packed_float32_array_constructor(
raw.GDExtensionUninitializedTypePtr(rawptr(&result.value)),
nil,
)
if length == 0 do return result, .None
size := i64(length)
arguments := [1]raw.GDExtensionConstTypePtr{const_type_pointer(rawptr(&size))}
resize_result: i64
api.packed_float32_array_resize(
type_pointer(rawptr(&result.value)),
&arguments[0],
type_pointer(rawptr(&resize_result)),
1,
)
if resize_result != 0 {
api.packed_float32_array_destructor(type_pointer(rawptr(&result.value)))
return {}, .Allocation_Failed
}
return result, .None
}
packed_float32_array_destroy :: proc(api: ^Bound_API, owner: ^Packed_Float32_Array) -> Call_Error {
if api == nil do return .Missing_API
if owner == nil || !owner.live do return .Invalid_Owner
api.packed_float32_array_destructor(type_pointer(rawptr(&owner.value)))
owner^ = {}
return .None
}
packed_float32_array_length :: proc(api: ^Bound_API, owner: ^Packed_Float32_Array) -> (int, Call_Error) {
if api == nil do return 0, .Missing_API
if owner == nil || !owner.live do return 0, .Invalid_Owner
result: i64
api.packed_float32_array_size(
type_pointer(rawptr(&owner.value)),
nil,
type_pointer(rawptr(&result)),
0,
)
if result < 0 || result > i64(max(int)) do return 0, .Out_Of_Range
return int(result), .None
}
packed_float32_array_write_base :: proc(api: ^Bound_API, owner: ^Packed_Float32_Array) -> (^f32, int, Call_Error) {
bound, bound_error := packed_float32_array_length(api, owner)
if bound_error != .None do return nil, 0, bound_error
if bound == 0 do return nil, 0, .None
base := api.packed_float32_array_index(type_pointer(rawptr(&owner.value)), 0)
if base == nil do return nil, 0, .Invalid_State
return base, bound, .None
}
packed_float32_array_read_base :: proc(api: ^Bound_API, owner: ^Packed_Float32_Array) -> (^f32, int, Call_Error) {
bound, bound_error := packed_float32_array_length(api, owner)
if bound_error != .None do return nil, 0, bound_error
if bound == 0 do return nil, 0, .None
base := api.packed_float32_array_index_const(const_type_pointer(rawptr(&owner.value)), 0)
if base == nil do return nil, 0, .Invalid_State
return base, bound, .None
}
packed_float32_array_read_exact :: proc(api: ^Bound_API, owner: ^Packed_Float32_Array, output: []f32) -> Call_Error {
if api == nil do return .Missing_API
if owner == nil || !owner.live do return .Invalid_Owner
if len(output) == 0 do return .None
base, bound, bound_error := packed_float32_array_read_base(api, owner)
if bound_error != .None do return bound_error
if base == nil || bound < len(output) do return .Out_Of_Range
mem.copy_non_overlapping(raw_data(output), rawptr(base), len(output)*size_of(f32))
return .None
}
// Packed_Float32_Staging is the float twin of Packed_Byte_Staging: it retains
// one engine-owned PackedFloat32Array so a pure Odin subsystem writes transforms
// into the engine's own backing store. Reuse requires a consumer that has finished
// reading and does not retain the array's storage. Returning from
// RenderingServer.multimesh_set_buffer alone does not establish this: the server
// can queue the call, physics interpolation can retain the submitted array, and
// renderer-side CPU caches can retain it after a direct upload.
//
// The qualified retained-write path uses direct render-thread submission,
// disabled physics interpolation, and a non-retaining renderer path. Its
// transform-only examples avoid per-instance getters/setters that create a CPU
// cache. These are caller constraints, not guarantees inferred by this owner.
// Its publish/pending sequence orders publication; it neither drains a server
// queue nor waits for GPU completion. Sole ownership and detachment detection
// follow the byte owner's structural rules; `length` counts f32 elements, and
// the write view is []f32 so no producer re-derives a byte stride from a float count.
Packed_Float32_Staging :: struct {
array: Packed_Float32_Array,
base: ^f32,
length: int,
publisher_role: Call_Role,
consumer_role: Call_Role,
generation: u64,
published: u64,
consumed: u64,
live: bool,
}
packed_float32_staging_create :: proc(
api: ^Bound_API,
length: int,
publisher_role: Call_Role,
consumer_role: Call_Role,
) -> (Packed_Float32_Staging, Call_Error) {
if !bound_api_is_complete(api) do return {}, .Missing_API
if length <= 0 || length > int(max(u32)) do return {}, .Out_Of_Range
if publisher_role == .Invalid || consumer_role == .Invalid do return {}, .Wrong_Role
array, array_error := packed_float32_array_zeroed(api, length)
if array_error != .None do return {}, array_error
result := Packed_Float32_Staging{
array=array,
length=length,
publisher_role=publisher_role,
consumer_role=consumer_role,
generation=api.generation,
live=true,
}
base, bound, base_error := packed_float32_array_write_base(api, &result.array)
if base_error != .None || bound != length {
packed_float32_array_destroy(api, &result.array)
return {}, .Invalid_State if base_error == .None else base_error
}
result.base = base
return result, .None
}
// packed_float32_staging_write returns the writable element view to the declared
// publisher role. It re-acquires the base pointer and re-reads the size on every
// call, so a detached or resized array fails here rather than corrupting a frame.
packed_float32_staging_write :: proc(
api: ^Bound_API,
staging: ^Packed_Float32_Staging,
role: Call_Role,
) -> ([]f32, Call_Error) {
if api == nil do return nil, .Missing_API
if staging == nil || !staging.live || !staging.array.live do return nil, .Invalid_Owner
if role != staging.publisher_role do return nil, .Wrong_Role
if api.generation != staging.generation do return nil, .Stale_Generation
base, bound, base_error := packed_float32_array_write_base(api, &staging.array)
if base_error != .None {
staging.live = false
return nil, base_error
}
if base != staging.base || bound != staging.length {
staging.live = false
return nil, .Invalid_State
}
return mem.slice_ptr(base, bound), .None
}
packed_float32_staging_publish :: proc(staging: ^Packed_Float32_Staging, role: Call_Role) -> (u64, Call_Error) {
if staging == nil || !staging.live do return 0, .Invalid_Owner
if role != staging.publisher_role do return 0, .Wrong_Role
next := staging.published + 1
sync.atomic_store_explicit(&staging.published, next, .Release)
return next, .None
}
packed_float32_staging_pending :: proc(staging: ^Packed_Float32_Staging, role: Call_Role) -> (u64, bool) {
if staging == nil || !staging.live do return 0, false
if role != staging.consumer_role do return 0, false
published := sync.atomic_load_explicit(&staging.published, .Acquire)
return published, published != staging.consumed
}
packed_float32_staging_destroy :: proc(api: ^Bound_API, staging: ^Packed_Float32_Staging) -> Call_Error {
if api == nil do return .Missing_API
if staging == nil || !staging.live && !staging.array.live do return .Invalid_Owner
error := Call_Error.None
if staging.array.live do error = packed_float32_array_destroy(api, &staging.array)
staging^ = {}
return error
}
// MultiMesh_Layout is the buffer layout one stream submits, restricted to the
// combinations the current consumers use: TRANSFORM_2D contributes eight
// floats per instance and TRANSFORM_3D twelve, and colors add four more.
// Custom data is refused outright — no current consumer uses it, and a layout
// nothing exercises has no measured retention contract.
MultiMesh_Layout :: struct {
transform_3d: bool,
colors: bool,
}
multimesh_layout_stride :: proc(layout: MultiMesh_Layout) -> int {
stride := 12 if layout.transform_3d else 8
if layout.colors do stride += 4
return stride
}
// MultiMesh_Stream is one scene-owned MultiMesh RID's per-frame submission
// pipeline — the whole acquire/allocate/write/publish/submit/replace/retire
// sequence in one owner. The RID stays a scene-owned borrow: every server
// allocation here targets it, and nothing here ever frees server storage, so
// releasing the MultiMesh resource remains the scene's own act. The staged
// array is the one thing this owner owns and destroys.
//
// open allocates the engine's server data at the declared capacity and one
// zeroed staged store of exactly capacity*stride floats. write hands the
// producer the staged view; the view is valid through the matching publish
// and no further — the staging owner's own contract. publish marks the
// frame. submit posts the staged content to the server: it refuses when no
// new publication is pending, refuses an unset or zero RID handle before any
// server call, and advances the submitted sequence. A resource the scene has
// freed keeps a non-zero RID that this check cannot distinguish — the
// engine's own server diagnostic names the dead RID and the call no-ops, and
// the recovery is close plus a fresh open against the replacement resource. resize
// runs create-first choreography: the replacement staged store is created,
// the same RID is re-allocated at the new capacity, the old pair becomes the
// pending retirement, and only a pending retirement that refuses to die fails
// the resize — the new capacity is already live at that point and the
// refusing owner stays reachable in `retiring` for every later retry. close
// retires the pending pair, destroys the active store, and touches nothing
// else.
//
// The staged store's contract is Packed_Float32_Staging's, unchanged: reuse
// requires a consumer that has finished reading and retains nothing, and
// returning from multimesh_set_buffer does not establish that. The qualified
// retained-write path needs direct render-thread submission, disabled physics
// interpolation, and a non-retaining renderer path; per-instance
// getters/setters create a CPU cache that invalidates reuse. These remain
// caller constraints this owner does not verify. The sequence counters order
// publication and submission; they neither drain a server queue nor wait for
// GPU completion.
MultiMesh_Stream :: struct {
rid: raw.RID,
layout: MultiMesh_Layout,
stride: int,
capacity: int,
staging: Packed_Float32_Staging,
retiring: Packed_Float32_Staging,
submitted: u64,
live: bool,
}
// multimesh_stream_open binds one scene-owned RID at one capacity. A zero
// capacity or an engine-invalid RID is a refusal before any server call.
multimesh_stream_open :: proc(call: Call, rid: raw.RID, layout: MultiMesh_Layout, capacity: int) -> (MultiMesh_Stream, Call_Error) {
if error := validate_call(call); error != .None do return {}, error
if !rid_valid(call.api, rid) do return {}, .Invalid_Handle
if capacity <= 0 || capacity > 1_000_000 do return {}, .Out_Of_Range
server, server_error := godot.singleton_rendering_server(generated_call(call))
if server_error != .None do return {}, generated_error(server_error)
format := raw.raw_godot_class_renderingserver_enum_multimeshtransformformat_value_multimesh_transform_2d
if layout.transform_3d do format = raw.raw_godot_class_renderingserver_enum_multimeshtransformformat_value_multimesh_transform_3d
if allocate_error := godot.rendering_server_multimesh_allocate_data(
generated_call(call),
&server,
rid,
i32(capacity),
format,
layout.colors,
false,
false,
); allocate_error != .None do return {}, generated_error(allocate_error)
staging, staging_error := packed_float32_staging_create(call.api, capacity*multimesh_layout_stride(layout), call.role, call.role)
if staging_error != .None do return {}, staging_error
return MultiMesh_Stream{
rid=rid,
layout=layout,
stride=multimesh_layout_stride(layout),
capacity=capacity,
staging=staging,
live=true,
}, .None
}
// multimesh_stream_owned reports whether the stream still owns anything: a
// live owner, an active staged store, or a pending retirement. Setup refuses
// to open over a stream that owns anything, and shutdown runs until this is
// false.
multimesh_stream_owned :: proc(stream: ^MultiMesh_Stream) -> bool {
if stream == nil do return false
return stream.live || stream.staging.live || stream.staging.array.live ||
stream.retiring.live || stream.retiring.array.live
}
// multimesh_stream_write returns the staged view for the stream's publisher
// role. Detachment and resizing of the underlying engine array fail here,
// killing the stream's store exactly like the bare staging owner.
multimesh_stream_write :: proc(call: Call, stream: ^MultiMesh_Stream) -> ([]f32, Call_Error) {
if stream == nil || !stream.live do return nil, .Invalid_Owner
return packed_float32_staging_write(call.api, &stream.staging, call.role)
}
// multimesh_stream_publish marks one written frame. The returned sequence is
// the value the next submit will record.
multimesh_stream_publish :: proc(stream: ^MultiMesh_Stream, role: Call_Role) -> (u64, Call_Error) {
if stream == nil || !stream.live do return 0, .Invalid_Owner
return packed_float32_staging_publish(&stream.staging, role)
}
// multimesh_stream_submit posts the staged content to the scene's RID.
// Without a new publication since the last submit it refuses with
// Invalid_State — a repeated submit would republish stale bytes and hide a
// producer-side choreography bug. A zero or unset RID handle is a
// Wrong_Device refusal, named before the engine call; a RID whose resource
// the scene freed stays non-zero, is invisible to that check, and reaches
// the server, which rejects it with its own diagnostic while the staged
// store stays intact. Scene RID liveness is a caller constraint; close plus
// open against the replacement resource is the named recovery.
multimesh_stream_submit :: proc(call: Call, stream: ^MultiMesh_Stream) -> Call_Error {
if error := validate_call(call); error != .None do return error
if stream == nil || !stream.live do return .Invalid_Owner
published := sync.atomic_load_explicit(&stream.staging.published, .Acquire)
if published == stream.submitted do return .Invalid_State
if !rid_valid(call.api, stream.rid) do return .Wrong_Device
server, server_error := godot.singleton_rendering_server(generated_call(call))
if server_error != .None do return generated_error(server_error)
if submit_error := godot.rendering_server_multimesh_set_buffer(
generated_call(call),
&server,
stream.rid,
stream.staging.array.value,
); submit_error != .None do return generated_error(submit_error)
stream.submitted = published
return .None
}
// multimesh_stream_resize moves one live stream to a new capacity on the
// same RID. The replacement store is created and the server re-allocated
// before anything is adopted, so every earlier failure leaves the previous
// active pair untouched. After adoption the pending pair is destroyed once;
// a refusing destruction fails the resize with the new capacity already live
// and the owner retained in `retiring` for the next retry.
multimesh_stream_resize :: proc(call: Call, stream: ^MultiMesh_Stream, capacity: int) -> Call_Error {
if error := validate_call(call); error != .None do return error
if stream == nil || !stream.live do return .Invalid_Owner
if capacity <= 0 || capacity > 1_000_000 do return .Out_Of_Range
if !rid_valid(call.api, stream.rid) do return .Wrong_Device
if stream.retiring.live || stream.retiring.array.live {
if destroy_error := multimesh_stream_retire_pending(call, stream); destroy_error != .None do return destroy_error
}
replacement, replacement_error := packed_float32_staging_create(call.api, capacity*stream.stride, stream.staging.publisher_role, stream.staging.consumer_role)
if replacement_error != .None do return replacement_error
server, server_error := godot.singleton_rendering_server(generated_call(call))
if server_error != .None {
packed_float32_staging_destroy(call.api, &replacement)
return generated_error(server_error)
}
format := raw.raw_godot_class_renderingserver_enum_multimeshtransformformat_value_multimesh_transform_2d
if stream.layout.transform_3d do format = raw.raw_godot_class_renderingserver_enum_multimeshtransformformat_value_multimesh_transform_3d
if allocate_error := godot.rendering_server_multimesh_allocate_data(
generated_call(call),
&server,
stream.rid,
i32(capacity),
format,
stream.layout.colors,
false,
false,
); allocate_error != .None {
packed_float32_staging_destroy(call.api, &replacement)
return generated_error(allocate_error)
}
stream.retiring = stream.staging
stream.staging = replacement
stream.capacity = capacity
stream.submitted = 0
return multimesh_stream_retire_pending(call, stream)
}
// multimesh_stream_retire_pending destroys the pending retirement once. An
// engine refusal keeps the owner reachable for the next attempt; a stream
// with nothing pending reports success.
multimesh_stream_retire_pending :: proc(call: Call, stream: ^MultiMesh_Stream) -> Call_Error {
if stream == nil do return .Invalid_Owner
if !stream.retiring.live && !stream.retiring.array.live do return .None
return packed_float32_staging_destroy(call.api, &stream.retiring)
}
// multimesh_stream_close retires every staged store the stream owns and
// zeroes the owner. The scene's RID and its server storage are untouched.
// A refusal names which half failed and leaves the survivor reachable in the
// owner for a repeated close.
multimesh_stream_close :: proc(call: Call, stream: ^MultiMesh_Stream) -> Call_Error {
if stream == nil do return .Invalid_Owner
if !stream.live && !stream.staging.live && !stream.staging.array.live do return .Invalid_Owner
result := Call_Error.None
if pending_error := multimesh_stream_retire_pending(call, stream); pending_error != .None do result = pending_error
if stream.staging.live || stream.staging.array.live {
if active_error := packed_float32_staging_destroy(call.api, &stream.staging); active_error != .None && result == .None do result = active_error
}
if result == .None {
stream^ = {}
}
return result
}
empty_array_create :: proc(api: ^Bound_API) -> raw.Array {
result: raw.Array
api.array_constructor(raw.GDExtensionUninitializedTypePtr(rawptr(&result)), nil)
return result
}
empty_string_create :: proc(api: ^Bound_API) -> raw.String {
result: raw.String
api.string_constructor(raw.GDExtensionUninitializedTypePtr(rawptr(&result)), nil)
return result
}
godot_string_from_utf8 :: proc(api: ^Bound_API, text: string) -> (raw.String, Call_Error) {
if api == nil || len(text) == 0 || len(text) > int(max(i32)) do return {}, .Out_Of_Range
result: raw.String
status := api.string_new_utf8(
raw.GDExtensionUninitializedStringPtr(rawptr(&result)),
cast(^i8)raw_data(text),
raw.GDExtensionInt(len(text)),
)
if status != 0 do return {}, .Godot_Error
return result, .None
}
rd_shader_source_set_compute_source :: proc(
call: Call,
object: raw.GDExtensionObjectPtr,
source: string,
) -> Call_Error {
if error := validate_call(call); error != .None do return error
if object == nil do return .Invalid_Handle
text, text_error := godot_string_from_utf8(call.api, source)
if text_error != .None do return text_error
defer call.api.string_destructor(type_pointer(rawptr(&text)))
stage := raw.raw_godot_class_renderingdevice_enum_shaderstage_value_shader_stage_compute
handle: godot.RDShaderSource
handle.ptr = object
return generated_error(godot.rd_shader_source_set_stage_source(generated_call(call), &handle, stage, text))
}
rendering_device_compile_spirv :: proc(
call: Call,
device: Device_Borrow,
source: RD_Shader_Source_Borrow,
) -> (Owned_Ref, Call_Error) {
if error := device_call_valid(call, device); error != .None do return {}, error
if rawptr(source) == nil do return {}, .Invalid_Handle
device_handle: godot.RenderingDevice
device_handle.ptr = device.object
source_handle: godot.RDShaderSource
source_handle.ptr = raw.GDExtensionObjectPtr(source)
result, generated_failure := godot.rendering_device_shader_compile_spirv_from_source(
generated_call(call),
&device_handle,
&source_handle,
true,
)
if generated_failure != .None do return {}, generated_error(generated_failure)
if !result.live || rawptr(result.handle.ptr) == nil do return {}, .Invalid_Handle
return {value=result.handle.ptr, live=true}, .None
}
rendering_device_create_shader :: proc(
call: Call,
device: Device_Borrow,
spirv: RD_Shader_SPIRV_Borrow,
) -> (Owned_RID, Call_Error) {
if error := device_call_valid(call, device); error != .None do return {}, error
if rawptr(spirv) == nil do return {}, .Invalid_Handle
name := empty_string_create(call.api)
defer call.api.string_destructor(type_pointer(rawptr(&name)))
device_handle: godot.RenderingDevice
device_handle.ptr = device.object
spirv_handle: godot.RDShaderSPIRV
spirv_handle.ptr = raw.GDExtensionObjectPtr(spirv)
result, generated_failure := godot.rendering_device_shader_create_from_spirv(
generated_call(call),
&device_handle,
&spirv_handle,
name,
)
if generated_failure != .None do return {}, generated_error(generated_failure)
return new_owned_rid(call, device, .Shader, result)
}
rendering_device_create_shader_from_glsl :: proc(
call: Call,
device: Device_Borrow,
source: string,
) -> (Owned_RID, Call_Error) {
if error := device_call_valid(call, device); error != .None do return {}, error
generated_owner, generated_failure := godot.construct_rd_shader_source(generated_call(call))
if generated_failure != .None do return {}, generated_error(generated_failure)
if !generated_owner.live || rawptr(generated_owner.handle.ptr) == nil do return {}, .Allocation_Failed
source_owner := Owned_Ref{value=generated_owner.handle.ptr, live=true}
if error := rd_shader_source_set_compute_source(call, source_owner.value, source); error != .None {
_ = ref_counted_release(call, &source_owner)
return {}, error
}
spirv, compile_error := rendering_device_compile_spirv(call, device, RD_Shader_Source_Borrow(source_owner.value))
if release_error := ref_counted_release(call, &source_owner); release_error != .None do return {}, release_error
if compile_error != .None do return {}, compile_error
shader, shader_error := rendering_device_create_shader(call, device, RD_Shader_SPIRV_Borrow(spirv.value))
if release_error := ref_counted_release(call, &spirv); release_error != .None do return {}, release_error
return shader, shader_error
}
rendering_device_create_storage_buffer :: proc(
call: Call,
device: Device_Borrow,
size: u32,
initial_data: []u8,
) -> (Owned_RID, Call_Error) {
if error := device_call_valid(call, device); error != .None do return {}, error
if size == 0 || (len(initial_data) != 0 && len(initial_data) != int(size)) do return {}, .Out_Of_Range
data, data_error := packed_byte_array_from_bytes(call.api, initial_data)
if data_error != .None do return {}, data_error
defer packed_byte_array_destroy(call.api, &data)
usage := raw.RenderingDevice_StorageBufferUsage(0)
creation := raw.RenderingDevice_BufferCreationBits(0)
device_handle: godot.RenderingDevice
device_handle.ptr = device.object
result, generated_failure := godot.rendering_device_storage_buffer_create(
generated_call(call),
&device_handle,
size,
data.value,
usage,
creation,
)
if generated_failure != .None do return {}, generated_error(generated_failure)
return new_owned_rid(call, device, .Storage_Buffer, result)
}
// rendering_device_buffer_update_staged is the parity path: the engine performs
// the only copy. No packed array is constructed, resized, or destroyed here, no
// element crosses the boundary individually, and the retained array's backing
// store is the same memory the publisher wrote in place.
//
// The consumer role is checked against the owner's declared role, so a Render
// consumer cannot submit a Main-only owner and the reverse.
rendering_device_buffer_update_staged :: proc(
call: Call,
device: Device_Borrow,
buffer: Borrowed_RID,
offset: u32,
staging: ^Packed_Byte_Staging,
) -> Call_Error {
if error := device_call_valid(call, device); error != .None do return error
if buffer.kind != .Storage_Buffer do return .Wrong_RID_Kind
if !rid_matches_device(call.api, buffer, device) do return .Wrong_Device
if staging == nil || !staging.live || !staging.array.live do return .Invalid_Owner
if call.role != staging.consumer_role do return .Wrong_Role
if call.api.generation != staging.generation do return .Stale_Generation
// Re-establish the bound from the engine on the consuming side too: a resize
// or detachment between publication and submission must fail rather than
// submit a stale length.
bound, bound_error := packed_byte_array_length(call.api, &staging.array)
if bound_error != .None {
staging.live = false
return bound_error
}
if bound != staging.length {
staging.live = false
return .Invalid_State
}
if bound == 0 || bound > int(max(u32)) do return .Out_Of_Range
device_handle: godot.RenderingDevice
device_handle.ptr = device.object
result, generated_failure := godot.rendering_device_buffer_update(
generated_call(call),
&device_handle,
buffer.value,
offset,
u32(bound),
staging.array.value,
)
if generated_failure != .None do return generated_error(generated_failure)
if result != raw.Error(0) do return .Godot_Error
staging.consumed = sync.atomic_load_explicit(&staging.published, .Acquire)
return .None
}
rendering_device_buffer_update :: proc(
call: Call,
device: Device_Borrow,
buffer: Borrowed_RID,
offset: u32,
bytes: []u8,
) -> Call_Error {
if error := device_call_valid(call, device); error != .None do return error
if buffer.kind != .Storage_Buffer do return .Wrong_RID_Kind
if !rid_matches_device(call.api, buffer, device) do return .Wrong_Device
if len(bytes) == 0 || len(bytes) > int(max(u32)) do return .Out_Of_Range
data, data_error := packed_byte_array_from_bytes(call.api, bytes)
if data_error != .None do return data_error
defer packed_byte_array_destroy(call.api, &data)
device_handle: godot.RenderingDevice
device_handle.ptr = device.object
result, generated_failure := godot.rendering_device_buffer_update(
generated_call(call),
&device_handle,
buffer.value,
offset,
u32(len(bytes)),
data.value,
)
if generated_failure != .None do return generated_error(generated_failure)
if result != raw.Error(0) do return .Godot_Error
return .None
}
rendering_device_buffer_get_data :: proc(
call: Call,
device: Device_Borrow,
buffer: Borrowed_RID,
offset, size: u32,
) -> (Packed_Byte_Array, Call_Error) {
if error := device_call_valid(call, device); error != .None do return {}, error
if device.kind != .Local do return {}, .Wrong_Device
if buffer.kind != .Storage_Buffer do return {}, .Wrong_RID_Kind
if !rid_matches_device(call.api, buffer, device) do return {}, .Wrong_Device
if device.local_owner.work_recorded && !device.local_owner.synchronized do return {}, .Synchronization_Required
device_handle: godot.RenderingDevice
device_handle.ptr = device.object
result, generated_failure := godot.rendering_device_buffer_get_data(
generated_call(call),
&device_handle,
buffer.value,
offset,
size,
)
if generated_failure != .None do return {}, generated_error(generated_failure)
// The generated owner's single engine reference moves into the facade's
// owner; zeroing the generated owner keeps it from releasing the same
// reference twice.
moved := Packed_Byte_Array{value=result.value, live=true}
result = {}
return moved, .None
}
rendering_device_create_compute_pipeline :: proc(
call: Call,
device: Device_Borrow,
shader: Borrowed_RID,
) -> (Owned_RID, Call_Error) {
if error := device_call_valid(call, device); error != .None do return {}, error
if shader.kind != .Shader do return {}, .Wrong_RID_Kind
if !rid_matches_device(call.api, shader, device) do return {}, .Wrong_Device
specializations := empty_array_create(call.api)
defer call.api.array_destructor(type_pointer(rawptr(&specializations)))
device_handle: godot.RenderingDevice
device_handle.ptr = device.object
result, generated_failure := godot.rendering_device_compute_pipeline_create(
generated_call(call),
&device_handle,
shader.value,
godot.Typed_Array(godot.RDPipelineSpecializationConstant){value=specializations},
)
if generated_failure != .None do return {}, generated_error(generated_failure)
return new_owned_rid(call, device, .Compute_Pipeline, result)
}
object_postinitialize :: proc(call: Call, object: raw.GDExtensionObjectPtr) -> Call_Error {
if error := validate_call(call); error != .None do return error
if object == nil do return .Invalid_Handle
handle := godot.Object{ptr=object}
return generated_error(godot.object_notification(generated_call(call), &handle, 0, false))
}
rd_uniform_set_type :: proc(call: Call, object: raw.GDExtensionObjectPtr, type: Uniform_Type) -> Call_Error {
if error := validate_call(call); error != .None do return error
if object == nil || (type != .Image && type != .Storage_Buffer) do return .Invalid_Handle
handle: godot.RDUniform
handle.ptr = object
return generated_error(godot.rd_uniform_set_uniform_type(generated_call(call), &handle, raw.RenderingDevice_UniformType(type)))
}
rd_uniform_set_binding :: proc(call: Call, object: raw.GDExtensionObjectPtr, binding: u32) -> Call_Error {
if error := validate_call(call); error != .None do return error
if object == nil do return .Invalid_Handle
handle: godot.RDUniform
handle.ptr = object
return generated_error(godot.rd_uniform_set_binding(generated_call(call), &handle, i32(binding)))
}
rd_uniform_add_rid :: proc(
call: Call,
api_device: Device_Borrow,
object: raw.GDExtensionObjectPtr,
rid: Borrowed_RID,
) -> Call_Error {
if error := device_call_valid(call, api_device); error != .None do return error
if object == nil do return .Invalid_Handle
if !rid_matches_device(call.api, rid, api_device) do return .Wrong_Device
handle: godot.RDUniform
handle.ptr = object
return generated_error(godot.rd_uniform_add_id(generated_call(call), &handle, rid.value))
}
create_rd_uniform :: proc(
call: Call,
device: Device_Borrow,
binding: Uniform_Binding,
) -> (Owned_Ref, Call_Error) {
if !rid_matches_device(call.api, binding.rid, device) do return {}, .Wrong_Device
generated_owner, generated_failure := godot.construct_rd_uniform(generated_call(call))
if generated_failure != .None do return {}, generated_error(generated_failure)
if !generated_owner.live || rawptr(generated_owner.handle.ptr) == nil do return {}, .Allocation_Failed
owner := Owned_Ref{value=generated_owner.handle.ptr, live=true}
if error := rd_uniform_set_type(call, owner.value, binding.type); error != .None {
_ = ref_counted_release(call, &owner)
return {}, error
}
if error := rd_uniform_set_binding(call, owner.value, binding.binding); error != .None {
_ = ref_counted_release(call, &owner)
return {}, error
}
if error := rd_uniform_add_rid(call, device, owner.value, binding.rid); error != .None {
_ = ref_counted_release(call, &owner)
return {}, error
}
return owner, .None
}
rendering_device_create_uniform_set :: proc(
call: Call,
device: Device_Borrow,
bindings: []Uniform_Binding,
shader: Borrowed_RID,
set_index: u32,
) -> (Owned_RID, Call_Error) {
if error := device_call_valid(call, device); error != .None do return {}, error
if len(bindings) == 0 || len(bindings) > MAX_UNIFORM_BINDINGS do return {}, .Out_Of_Range
if shader.kind != .Shader do return {}, .Wrong_RID_Kind
if !rid_matches_device(call.api, shader, device) do return {}, .Wrong_Device
for binding, index in bindings {
if binding.type != .Image && binding.type != .Storage_Buffer do return {}, .Out_Of_Range
if !rid_matches_device(call.api, binding.rid, device) do return {}, .Wrong_Device
if binding.type == .Image && binding.rid.kind != .Texture_Borrow do return {}, .Wrong_RID_Kind
if binding.type == .Storage_Buffer && binding.rid.kind != .Storage_Buffer do return {}, .Wrong_RID_Kind
for previous in bindings[:index] do if previous.binding == binding.binding do return {}, .Invalid_State
}
array := empty_array_create(call.api)
defer call.api.array_destructor(type_pointer(rawptr(&array)))
for binding in bindings {
uniform, uniform_error := create_rd_uniform(call, device, binding)
if uniform_error != .None do return {}, uniform_error
variant: raw.Variant
uniform_object := uniform.value
call.api.variant_from_object(
raw.GDExtensionUninitializedVariantPtr(rawptr(&variant)),
type_pointer(rawptr(&uniform_object)),
)
arguments := [1]raw.GDExtensionConstTypePtr{const_type_pointer(rawptr(&variant))}
call.api.array_append(type_pointer(rawptr(&array)), &arguments[0], type_pointer(nil), 1)
call.api.variant_destroy(raw.GDExtensionVariantPtr(rawptr(&variant)))
if release_error := ref_counted_release(call, &uniform); release_error != .None do return {}, release_error
}
device_handle: godot.RenderingDevice
device_handle.ptr = device.object
result, generated_failure := godot.rendering_device_uniform_set_create(
generated_call(call),
&device_handle,
godot.Typed_Array(godot.RDUniform){value=array},
shader.value,
set_index,
)
if generated_failure != .None do return {}, generated_error(generated_failure)
return new_owned_rid(call, device, .Uniform_Set, result)
}
rendering_device_compute_list_begin :: proc(call: Call, device: Device_Borrow) -> (Compute_List, Call_Error) {
if error := device_call_valid(call, device); error != .None do return {}, error
device_handle: godot.RenderingDevice
device_handle.ptr = device.object
result, generated_failure := godot.rendering_device_compute_list_begin(generated_call(call), &device_handle)
if generated_failure != .None do return {}, generated_error(generated_failure)
return {
value=result,
device_kind=device.kind,
device=device.identity,
creator_role=call.role,
local_owner=device.local_owner,
open=true,
}, .None
}
compute_list_matches_device :: proc(list: ^Compute_List, device: Device_Borrow, call: Call) -> bool {
return list != nil && list.open && list.device_kind == device.kind && list.device == device.identity &&
list.creator_role == call.role && list.local_owner == device.local_owner
}
compute_list_bind_pipeline :: proc(
call: Call,
device: Device_Borrow,
list: ^Compute_List,
pipeline: Borrowed_RID,
) -> Call_Error {
if error := device_call_valid(call, device); error != .None do return error
if !compute_list_matches_device(list, device, call) do return .Invalid_State
if pipeline.kind != .Compute_Pipeline do return .Wrong_RID_Kind
if !rid_matches_device(call.api, pipeline, device) do return .Wrong_Device
device_handle: godot.RenderingDevice
device_handle.ptr = device.object
return generated_error(godot.rendering_device_compute_list_bind_compute_pipeline(
generated_call(call),
&device_handle,
list.value,
pipeline.value,
))
}
compute_list_bind_uniform_set :: proc(
call: Call,
device: Device_Borrow,
list: ^Compute_List,
uniform_set: Borrowed_RID,
set_index: u32,
) -> Call_Error {
if error := device_call_valid(call, device); error != .None do return error
if !compute_list_matches_device(list, device, call) do return .Invalid_State
if uniform_set.kind != .Uniform_Set do return .Wrong_RID_Kind
if !rid_matches_device(call.api, uniform_set, device) do return .Wrong_Device
device_handle: godot.RenderingDevice
device_handle.ptr = device.object
return generated_error(godot.rendering_device_compute_list_bind_uniform_set(
generated_call(call),
&device_handle,
list.value,
uniform_set.value,
set_index,
))
}
compute_list_dispatch :: proc(
call: Call,
device: Device_Borrow,
list: ^Compute_List,
x, y, z: u32,
) -> Call_Error {
if error := device_call_valid(call, device); error != .None do return error
if !compute_list_matches_device(list, device, call) do return .Invalid_State
if x == 0 || y == 0 || z == 0 do return .Out_Of_Range
device_handle: godot.RenderingDevice
device_handle.ptr = device.object
return generated_error(godot.rendering_device_compute_list_dispatch(
generated_call(call),
&device_handle,
list.value,
x,
y,
z,
))
}
compute_list_end :: proc(call: Call, device: Device_Borrow, list: ^Compute_List) -> Call_Error {
if error := device_call_valid(call, device); error != .None do return error
if !compute_list_matches_device(list, device, call) do return .Invalid_State
device_handle: godot.RenderingDevice
device_handle.ptr = device.object
if generated_failure := godot.rendering_device_compute_list_end(generated_call(call), &device_handle); generated_failure != .None {
return generated_error(generated_failure)
}
list^ = {}
if device.kind == .Local {
device.local_owner.work_recorded = true
device.local_owner.submitted = false
device.local_owner.synchronized = false
}
return .None
}
local_device_submit :: proc(call: Call, owner: ^Owned_Local_Device) -> Call_Error {
if error := validate_call(call); error != .None do return error
if call.role != .Main do return .Wrong_Role
if owner == nil || !owner.live || owner.object == nil do return .Invalid_Owner
if !owner.work_recorded || owner.submitted do return .Invalid_State
device_handle: godot.RenderingDevice
device_handle.ptr = owner.object
if generated_failure := godot.rendering_device_submit(generated_call(call), &device_handle); generated_failure != .None {
return generated_error(generated_failure)
}
owner.submitted = true
owner.synchronized = false
return .None
}
local_device_sync :: proc(call: Call, owner: ^Owned_Local_Device) -> Call_Error {
if error := validate_call(call); error != .None do return error
if call.role != .Main do return .Wrong_Role
if owner == nil || !owner.live || owner.object == nil do return .Invalid_Owner
if !owner.work_recorded || !owner.submitted || owner.synchronized do return .Invalid_State
device_handle: godot.RenderingDevice
device_handle.ptr = owner.object
if generated_failure := godot.rendering_device_sync(generated_call(call), &device_handle); generated_failure != .None {
return generated_error(generated_failure)
}
owner.synchronized = true
return .None
}
owned_rid_free :: proc(call: Call, device: Device_Borrow, owner: ^Owned_RID) -> Call_Error {
if error := device_call_valid(call, device); error != .None do return error
if owner == nil || !owner.live do return .Invalid_Owner
if owner.kind == .Texture_Borrow || owner.kind == .Invalid do return .Wrong_RID_Kind
if owner.device_kind != device.kind || owner.device != device.identity ||
owner.creator_role != call.role || owner.local_owner != device.local_owner {
return .Wrong_Device
}
if device.kind == .Local && device.local_owner.work_recorded && !device.local_owner.synchronized {
return .Synchronization_Required
}
if device.kind == .Local && device.local_owner.outstanding_rids == 0 do return .Invalid_State
if !rid_valid(call.api, owner.value) do return .Invalid_Handle
device_handle: godot.RenderingDevice
device_handle.ptr = device.object
if generated_failure := godot.rendering_device_free_rid(generated_call(call), &device_handle, owner.value); generated_failure != .None {
return generated_error(generated_failure)
}
if device.kind == .Local do device.local_owner.outstanding_rids -= 1
owner^ = {}
return .None
}