simple-nvk
Version:
Graphic libary, using Javascript and Node-Vulkan.<br> It has similarities to WebGPU in some places.<br> ## Features Abstractions of Vulkan features like staged buffers.<br> Much shorter to write than Vulkan.<br> ## Install ````console npm install s
234 lines (194 loc) • 7.55 kB
JavaScript
import { assertVulkan } from "../utils.mjs";
import Handle from "./handle.mjs";
import nvk from "nvk";
import * as enums from "../snvk-enum.mjs";
class BindingObj {
constructor(bufferHandle, binding, stride) {
this.owner = bufferHandle.owner; this.buffer = bufferHandle; this.binding = binding; this.stride = stride;
}
getAttribute(location, type, size, offset = 0) {
return new AttributeObj(this, location, type, size, offset);
}
}
class AttributeObj {
constructor(bindingObj, location, type, size, offset) {
this.owner = bindingObj.owner;
let format = getVkFormat(this.owner, type >> 4, size, type & 15);
this.binding = bindingObj; this.location = location; this.format = format; this.offset = offset;
}
}
class DescriptorObj {
constructor(bufferHandle, binding, type) {
this.buffer = bufferHandle; this.binding = binding; this.type = type;
}
}
export default class BufferHandle extends Handle {
constructor(owner, { size, usage, staging = enums.BUFFER_STAGING_DYNAMIC, readable = false }) {
super(owner);
let vkUsageBits = getVkBufferUsageBits(this.owner, usage, readable);
let hostBuffer = null;
if (staging === enums.BUFFER_STAGING_STATIC) {
hostBuffer = createVkHostBuffer(this.owner, size, vkUsageBits.host);
}
let localBuffer = createVkBuffer(this.owner,
size,
vkUsageBits.local,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
);
this.id = -1;
this.vksHost = hostBuffer;
this.vksLocal = localBuffer;
this.vkUsageBits = vkUsageBits;
this.staging = staging;
this.usage = usage;
this.size = size;
this.readable = readable;
}
destroy() {
this.super_destroy();
let { owner } = this;
if (this.staging === enums.BUFFER_STAGING_STATIC) {
destroyVkBuffer(owner, this.vksHost);
}
destroyVkBuffer(owner, this.vksLocal);
}
subData(offsetDst, data, offsetSrc, length = null) {
let dataPtr = { $: 0n };
if (length === null) length = data.buffer.size;
let offsetHost = offsetDst;
if (this.staging === enums.BUFFER_STAGING_DYNAMIC) {
this.vksHost = createVkHostBuffer(this.owner, length, this.vkUsageBits.host);
offsetHost = 0;
}
let result = vkMapMemory(this.device, this.vksHost.vkMemory, offsetHost, length, 0, dataPtr);
assertVulkan(result);
let dstView = new Uint8Array(ArrayBuffer.fromAddress(dataPtr.$, length));
let srcView = new Uint8Array(data.buffer).subarray(offsetSrc, offsetSrc + length);
dstView.set(srcView, 0);
vkUnmapMemory(this.device, this.vksHost.vkMemory);
copyVkBuffer(this.owner, this.vksHost.vkBuffer, offsetHost, this.vksLocal.vkBuffer, offsetDst, length);
if (this.staging === enums.BUFFER_STAGING_DYNAMIC) {
destroyVkBuffer(this.owner, this.vksHost);
}
}
readData(offset = 0, length = null) {
let dataPtr = { $: 0n };
if (length === null) length = this.size;
let offsetHost = offset;
if (this.staging === enums.BUFFER_STAGING_DYNAMIC) {
this.vksHost = createVkHostBuffer(this.owner, length, this.vkUsageBits.host);
offsetHost = 0;
}
copyVkBuffer(this.owner, this.vksLocal.vkBuffer, offset, this.vksHost.vkBuffer, offsetHost, length);
let result = vkMapMemory(this.device, this.vksHost.vkMemory, offsetHost, length, 0, dataPtr);
assertVulkan(result);
let buffer = ArrayBuffer.fromAddress(dataPtr.$, length);
if (this.staging === enums.BUFFER_STAGING_DYNAMIC) {
destroyVkBuffer(this.owner, this.vksHost);
}
return buffer;
}
copy(srcHandle, offsetSrc, dstHandle, offsetDst, size) {
copyVkBuffer(this.owner, srcHandle.vksLocal.vkBuffer, offsetSrc, dstHandle.vksLocal.vkBuffer, offsetDst, size);
}
getBinding(binding = 0, stride = 1) {
return new BindingObj(this, binding, stride);
}
getDescriptor(binding, type) {
return new DescriptorObj(this, binding, type)
}
}
function createVkHostBuffer(owner, size, bufferUsageFlags) {
return createVkBuffer(owner,
size,
bufferUsageFlags,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
);
}
function copyVkBuffer(owner, src, offsetSrc, dst, offsetDst, size) {
let commandCreateInfo = {
level: enums.COMMAND_LEVEL_PRIMARY,
usage: enums.COMMAND_USAGE_ONE_TIME,
queue: enums.COMMAND_QUEUE_TRANSFER,
}
let command = owner.createCommandBuffer(commandCreateInfo);
command.begin();
let { vkCommandBuffer } = command;
let bufferCopy = new VkBufferCopy();
bufferCopy.srcOffset = offsetSrc;
bufferCopy.dstOffset = offsetDst;
bufferCopy.size = size;
vkCmdCopyBuffer(vkCommandBuffer, src, dst, 1, [bufferCopy]);
command.end();
let submitInfo = {
commandBuffer: command,
blocking: false,
}
owner.submit(submitInfo);
command.destroy();
}
function createVkBuffer(owner, bufferSize, bufferUsageFlags, memoryPropertieFlags) {
let bufferInfo = new VkBufferCreateInfo();
bufferInfo.size = bufferSize;
bufferInfo.usage = bufferUsageFlags;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
bufferInfo.queueFamilyIndexCount = 0;
bufferInfo.pQueueFamilyIndices = null;
let buffer = new VkBuffer();
let result = vkCreateBuffer(owner.device, bufferInfo, null, buffer);
assertVulkan(result);
let memoryRequirements = new VkMemoryRequirements()
vkGetBufferMemoryRequirements(owner.device, buffer, memoryRequirements);
let memoryAllocateInfo = new VkMemoryAllocateInfo();
memoryAllocateInfo.allocationSize = memoryRequirements.size;
memoryAllocateInfo.memoryTypeIndex = findVkMemoryTypeIndex(owner,
memoryRequirements.memoryTypeBits, memoryPropertieFlags
);
let memory = new VkDeviceMemory();
result = vkAllocateMemory(owner.device, memoryAllocateInfo, null, memory);
assertVulkan(result);
vkBindBufferMemory(owner.device, buffer, memory, 0n);
return {
vkBuffer: buffer,
vkMemory: memory,
}
}
function destroyVkBuffer(owner, buffer) {
vkFreeMemory(owner.device, buffer.vkMemory);
vkDestroyBuffer(owner.device, buffer.vkBuffer, null);
}
function getVkFormat(owner, size, vec, type) {
let enumName = `VK_FORMAT_`
size *= 8;
switch (vec) {
case 1: enumName += `R${size}`; break;
case 2: enumName += `R${size}G${size}`; break;
case 3: enumName += `R${size}G${size}B${size}`; break;
case 4: enumName += `R${size}G${size}B${size}A${size}`; break;
}
switch (type) {
case enums.UINT: enumName += `_UINT`; break;
case enums.INT: enumName += `_SINT`; break;
case enums.FLOAT: enumName += `_SFLOAT`; break;
}
return nvk[enumName];
}
function getVkBufferUsageBits(owner, usage, readable) {
let host = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
let local = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
if (readable) {
host |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
local |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
}
local |= usage;
return { host, local };
}
function findVkMemoryTypeIndex(owner, typeFilter, properties) {
let memoryProperties = new VkPhysicalDeviceMemoryProperties();
vkGetPhysicalDeviceMemoryProperties(owner.physicalDevice, memoryProperties);
for (let i = 0; i < memoryProperties.memoryTypeCount; i++) {
if (typeFilter & (1 << i) && ((memoryProperties.memoryTypes[i].propertyFlags & properties))) {
return i;
}
}
}