adding Descriptors fundamental implementation
This commit is contained in:
14
README.md
14
README.md
@@ -96,10 +96,10 @@ vkCmdWaitEvents | ❌ Not implemented
|
||||
vkCmdWriteTimestamp | ❌ Not implemented
|
||||
vkCreateBuffer | ✅ Implemented
|
||||
vkCreateBufferView | ❌ Not implemented
|
||||
vkCreateCommandPool | ❌ Not implemented
|
||||
vkCreateCommandPool | ✅ Implemented
|
||||
vkCreateComputePipelines | ❌ Not implemented
|
||||
vkCreateDescriptorPool | ❌ Not implemented
|
||||
vkCreateDescriptorSetLayout | ❌ Not implemented
|
||||
vkCreateDescriptorPool | ⚙️ WIP
|
||||
vkCreateDescriptorSetLayout | ⚙️ WIP
|
||||
vkCreateDevice | ✅ Implemented
|
||||
vkCreateEvent | ❌ Not implemented
|
||||
vkCreateFence | ✅ Implemented
|
||||
@@ -117,9 +117,9 @@ vkCreateSemaphore | ❌ Not implemented
|
||||
vkCreateShaderModule | ❌ Not implemented
|
||||
vkDestroyBuffer | ✅ Implemented
|
||||
vkDestroyBufferView | ❌ Not implemented
|
||||
vkDestroyCommandPool | ❌ Not implemented
|
||||
vkDestroyDescriptorPool | ❌ Not implemented
|
||||
vkDestroyDescriptorSetLayout | ❌ Not implemented
|
||||
vkDestroyCommandPool | ✅ Implemented
|
||||
vkDestroyDescriptorPool | ⚙️ WIP
|
||||
vkDestroyDescriptorSetLayout | ⚙️ WIP
|
||||
vkDestroyDevice | ✅ Implemented
|
||||
vkDestroyEvent | ❌ Not implemented
|
||||
vkDestroyFence | ✅ Implemented
|
||||
@@ -144,7 +144,7 @@ vkEnumerateInstanceLayerProperties | ⚙️ WIP
|
||||
vkEnumeratePhysicalDevices | ✅ Implemented
|
||||
vkFlushMappedMemoryRanges | ❌ Not implemented
|
||||
vkFreeCommandBuffers | ✅ Implemented
|
||||
vkFreeDescriptorSets | ❌ Not implemented
|
||||
vkFreeDescriptorSets | ⚙️ WIP
|
||||
vkFreeMemory | ✅ Implemented
|
||||
vkGetBufferMemoryRequirements | ✅ Implemented
|
||||
vkGetDeviceMemoryCommitment | ❌ Not implemented
|
||||
|
||||
38
src/soft/SoftDescriptorPool.zig
git.filemode.normal_file
38
src/soft/SoftDescriptorPool.zig
git.filemode.normal_file
@@ -0,0 +1,38 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.DescriptorPool;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.DescriptorPoolCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
.freeDescriptorSets = freeDescriptorSets,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn freeDescriptorSets(interface: *Interface, sets: []*base.Dispatchable(base.DescriptorSet)) VkError!void {
|
||||
_ = interface;
|
||||
_ = sets;
|
||||
}
|
||||
32
src/soft/SoftDescriptorSetLayout.zig
git.filemode.normal_file
32
src/soft/SoftDescriptorSetLayout.zig
git.filemode.normal_file
@@ -0,0 +1,32 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.DescriptorSetLayout;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.DescriptorSetLayoutCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
@@ -10,6 +10,8 @@ const SoftQueue = @import("SoftQueue.zig");
|
||||
|
||||
const SoftBuffer = @import("SoftBuffer.zig");
|
||||
const SoftDeviceMemory = @import("SoftDeviceMemory.zig");
|
||||
const SoftDescriptorPool = @import("SoftDescriptorPool.zig");
|
||||
const SoftDescriptorSetLayout = @import("SoftDescriptorSetLayout.zig");
|
||||
const SoftFence = @import("SoftFence.zig");
|
||||
const SoftImage = @import("SoftImage.zig");
|
||||
const SoftImageView = @import("SoftImageView.zig");
|
||||
@@ -40,6 +42,8 @@ pub fn create(physical_device: *base.PhysicalDevice, allocator: std.mem.Allocato
|
||||
.allocateMemory = allocateMemory,
|
||||
.createBuffer = createBuffer,
|
||||
.createCommandPool = createCommandPool,
|
||||
.createDescriptorPool = createDescriptorPool,
|
||||
.createDescriptorSetLayout = createDescriptorSetLayout,
|
||||
.createFence = createFence,
|
||||
.createImage = createImage,
|
||||
.createImageView = createImageView,
|
||||
@@ -86,6 +90,16 @@ pub fn createBuffer(interface: *Interface, allocator: std.mem.Allocator, info: *
|
||||
return &buffer.interface;
|
||||
}
|
||||
|
||||
pub fn createDescriptorPool(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.DescriptorPoolCreateInfo) VkError!*base.DescriptorPool {
|
||||
const pool = try SoftDescriptorPool.create(interface, allocator, info);
|
||||
return &pool.interface;
|
||||
}
|
||||
|
||||
pub fn createDescriptorSetLayout(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.DescriptorSetLayoutCreateInfo) VkError!*base.DescriptorSetLayout {
|
||||
const layout = try SoftDescriptorSetLayout.create(interface, allocator, info);
|
||||
return &layout.interface;
|
||||
}
|
||||
|
||||
pub fn createFence(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.FenceCreateInfo) VkError!*base.Fence {
|
||||
const fence = try SoftFence.create(interface, allocator, info);
|
||||
return &fence.interface;
|
||||
|
||||
@@ -13,6 +13,8 @@ pub const SoftBuffer = @import("SoftBuffer.zig");
|
||||
pub const SoftCommandBuffer = @import("SoftCommandBuffer.zig");
|
||||
pub const SoftCommandPool = @import("SoftCommandPool.zig");
|
||||
pub const SoftDeviceMemory = @import("SoftDeviceMemory.zig");
|
||||
pub const SoftDescriptorPool = @import("SoftDescriptorPool.zig");
|
||||
pub const SoftDescriptorSetLayout = @import("SoftDescriptorSetLayout.zig");
|
||||
pub const SoftFence = @import("SoftFence.zig");
|
||||
pub const SoftImage = @import("SoftImage.zig");
|
||||
pub const SoftImageView = @import("SoftImageView.zig");
|
||||
|
||||
@@ -2,9 +2,12 @@ const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const VkError = @import("error_set.zig").VkError;
|
||||
const Dispatchable = @import("Dispatchable.zig").Dispatchable;
|
||||
|
||||
const Device = @import("Device.zig");
|
||||
|
||||
const DescriptorSet = @import("DescriptorSet.zig");
|
||||
|
||||
const Self = @This();
|
||||
pub const ObjectType: vk.ObjectType = .descriptor_pool;
|
||||
|
||||
@@ -15,6 +18,7 @@ vtable: *const VTable,
|
||||
|
||||
pub const VTable = struct {
|
||||
destroy: *const fn (*Self, std.mem.Allocator) void,
|
||||
freeDescriptorSets: *const fn (*Self, []*Dispatchable(DescriptorSet)) VkError!void,
|
||||
};
|
||||
|
||||
pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.DescriptorPoolCreateInfo) VkError!Self {
|
||||
@@ -22,9 +26,14 @@ pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.Descr
|
||||
return .{
|
||||
.owner = device,
|
||||
.flags = info.flags,
|
||||
.vtable = undefined,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn destroy(self: *Self, allocator: std.mem.Allocator) void {
|
||||
self.vtable.destroy(self, allocator);
|
||||
}
|
||||
|
||||
pub inline fn freeDescriptorSets(self: *Self, sets: []*Dispatchable(DescriptorSet)) VkError!void {
|
||||
try self.vtable.freeDescriptorSets(self, sets);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.Descr
|
||||
return .{
|
||||
.owner = device,
|
||||
.layouts = layouts,
|
||||
.vtable = undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ const Self = @This();
|
||||
pub const ObjectType: vk.ObjectType = .descriptor_set_layout;
|
||||
|
||||
owner: *Device,
|
||||
bindings: []const vk.DescriptorSetLayoutBinding,
|
||||
bindings: ?[]const vk.DescriptorSetLayoutBinding,
|
||||
|
||||
vtable: *const VTable,
|
||||
|
||||
@@ -17,9 +17,15 @@ pub const VTable = struct {
|
||||
};
|
||||
|
||||
pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.DescriptorSetLayoutCreateInfo) VkError!Self {
|
||||
const bindings = if (info.p_bindings) |bindings|
|
||||
allocator.dupe(vk.DescriptorSetLayoutBinding, bindings[0..info.binding_count]) catch return VkError.OutOfHostMemory
|
||||
else
|
||||
null;
|
||||
|
||||
return .{
|
||||
.owner = device,
|
||||
.bindings = allocator.dupe(info.bindings[0..info.binding_count]) catch return VkError.OutOfHostMemory,
|
||||
.bindings = bindings,
|
||||
.vtable = undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ const Buffer = @import("Buffer.zig");
|
||||
const CommandBuffer = @import("CommandBuffer.zig");
|
||||
const CommandPool = @import("CommandPool.zig");
|
||||
const DeviceMemory = @import("DeviceMemory.zig");
|
||||
const DescriptorPool = @import("DescriptorPool.zig");
|
||||
const DescriptorSet = @import("DescriptorSet.zig");
|
||||
const DescriptorSetLayout = @import("DescriptorSetLayout.zig");
|
||||
const Fence = @import("Fence.zig");
|
||||
const Image = @import("Image.zig");
|
||||
const ImageView = @import("ImageView.zig");
|
||||
@@ -36,6 +39,8 @@ pub const DispatchTable = struct {
|
||||
allocateMemory: *const fn (*Self, std.mem.Allocator, *const vk.MemoryAllocateInfo) VkError!*DeviceMemory,
|
||||
createBuffer: *const fn (*Self, std.mem.Allocator, *const vk.BufferCreateInfo) VkError!*Buffer,
|
||||
createCommandPool: *const fn (*Self, std.mem.Allocator, *const vk.CommandPoolCreateInfo) VkError!*CommandPool,
|
||||
createDescriptorPool: *const fn (*Self, std.mem.Allocator, *const vk.DescriptorPoolCreateInfo) VkError!*DescriptorPool,
|
||||
createDescriptorSetLayout: *const fn (*Self, std.mem.Allocator, *const vk.DescriptorSetLayoutCreateInfo) VkError!*DescriptorSetLayout,
|
||||
createFence: *const fn (*Self, std.mem.Allocator, *const vk.FenceCreateInfo) VkError!*Fence,
|
||||
createImage: *const fn (*Self, std.mem.Allocator, *const vk.ImageCreateInfo) VkError!*Image,
|
||||
createImageView: *const fn (*Self, std.mem.Allocator, *const vk.ImageViewCreateInfo) VkError!*ImageView,
|
||||
@@ -96,6 +101,14 @@ pub inline fn createBuffer(self: *Self, allocator: std.mem.Allocator, info: *con
|
||||
return self.dispatch_table.createBuffer(self, allocator, info);
|
||||
}
|
||||
|
||||
pub inline fn createDescriptorPool(self: *Self, allocator: std.mem.Allocator, info: *const vk.DescriptorPoolCreateInfo) VkError!*DescriptorPool {
|
||||
return self.dispatch_table.createDescriptorPool(self, allocator, info);
|
||||
}
|
||||
|
||||
pub inline fn createDescriptorSetLayout(self: *Self, allocator: std.mem.Allocator, info: *const vk.DescriptorSetLayoutCreateInfo) VkError!*DescriptorSetLayout {
|
||||
return self.dispatch_table.createDescriptorSetLayout(self, allocator, info);
|
||||
}
|
||||
|
||||
pub inline fn createFence(self: *Self, allocator: std.mem.Allocator, info: *const vk.FenceCreateInfo) VkError!*Fence {
|
||||
return self.dispatch_table.createFence(self, allocator, info);
|
||||
}
|
||||
|
||||
@@ -94,9 +94,12 @@ const physical_device_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComp
|
||||
functionMapEntryPoint("vkGetPhysicalDeviceSparseImageFormatProperties"),
|
||||
});
|
||||
|
||||
const device_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComptime(.{
|
||||
const device_pfn_map = block: {
|
||||
@setEvalBranchQuota(65535);
|
||||
break :block std.StaticStringMap(vk.PfnVoidFunction).initComptime(.{
|
||||
functionMapEntryPoint("vkAllocateCommandBuffers"),
|
||||
functionMapEntryPoint("vkAllocateDescriptorSets"),
|
||||
functionMapEntryPoint("vkAllocateDescriptorSets"),
|
||||
functionMapEntryPoint("vkAllocateMemory"),
|
||||
functionMapEntryPoint("vkBeginCommandBuffer"),
|
||||
functionMapEntryPoint("vkBindBufferMemory"),
|
||||
@@ -117,17 +120,22 @@ const device_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComptime(.{
|
||||
functionMapEntryPoint("vkCmdFillBuffer"),
|
||||
functionMapEntryPoint("vkCreateBuffer"),
|
||||
functionMapEntryPoint("vkCreateCommandPool"),
|
||||
functionMapEntryPoint("vkCreateDescriptorPool"),
|
||||
functionMapEntryPoint("vkCreateDescriptorSetLayout"),
|
||||
functionMapEntryPoint("vkCreateFence"),
|
||||
functionMapEntryPoint("vkCreateImage"),
|
||||
functionMapEntryPoint("vkCreateImageView"),
|
||||
functionMapEntryPoint("vkDestroyBuffer"),
|
||||
functionMapEntryPoint("vkDestroyCommandPool"),
|
||||
functionMapEntryPoint("vkDestroyDescriptorPool"),
|
||||
functionMapEntryPoint("vkDestroyDescriptorSetLayout"),
|
||||
functionMapEntryPoint("vkDestroyDevice"),
|
||||
functionMapEntryPoint("vkDestroyFence"),
|
||||
functionMapEntryPoint("vkDestroyImage"),
|
||||
functionMapEntryPoint("vkDestroyImageView"),
|
||||
functionMapEntryPoint("vkEndCommandBuffer"),
|
||||
functionMapEntryPoint("vkFreeCommandBuffers"),
|
||||
functionMapEntryPoint("vkFreeDescriptorSets"),
|
||||
functionMapEntryPoint("vkFreeMemory"),
|
||||
functionMapEntryPoint("vkGetBufferMemoryRequirements"),
|
||||
functionMapEntryPoint("vkGetDeviceQueue"),
|
||||
@@ -141,7 +149,8 @@ const device_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComptime(.{
|
||||
functionMapEntryPoint("vkResetFences"),
|
||||
functionMapEntryPoint("vkUnmapMemory"),
|
||||
functionMapEntryPoint("vkWaitForFences"),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// ICD Interface =============================================================================================================================================
|
||||
|
||||
@@ -540,6 +549,36 @@ pub export fn strollCreateCommandPool(p_device: vk.Device, info: *const vk.Comma
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub export fn strollCreateDescriptorPool(p_device: vk.Device, info: *const vk.DescriptorPoolCreateInfo, callbacks: ?*const vk.AllocationCallbacks, p_pool: *vk.DescriptorPool) callconv(vk.vulkan_call_conv) vk.Result {
|
||||
entryPointBeginLogTrace(.vkCreateDescriptorPool);
|
||||
defer entryPointEndLogTrace();
|
||||
|
||||
if (info.s_type != .descriptor_pool_create_info) {
|
||||
return .error_validation_failed;
|
||||
}
|
||||
|
||||
const allocator = VulkanAllocator.init(callbacks, .object).allocator();
|
||||
const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err);
|
||||
const pool = device.createDescriptorPool(allocator, info) catch |err| return toVkResult(err);
|
||||
p_pool.* = (NonDispatchable(DescriptorPool).wrap(allocator, pool) catch |err| return toVkResult(err)).toVkHandle(vk.DescriptorPool);
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub export fn strollCreateDescriptorSetLayout(p_device: vk.Device, info: *const vk.DescriptorSetLayoutCreateInfo, callbacks: ?*const vk.AllocationCallbacks, p_layout: *vk.DescriptorSetLayout) callconv(vk.vulkan_call_conv) vk.Result {
|
||||
entryPointBeginLogTrace(.vkCreateDescriptorSetLayout);
|
||||
defer entryPointEndLogTrace();
|
||||
|
||||
if (info.s_type != .descriptor_set_layout_create_info) {
|
||||
return .error_validation_failed;
|
||||
}
|
||||
|
||||
const allocator = VulkanAllocator.init(callbacks, .object).allocator();
|
||||
const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err);
|
||||
const layout = device.createDescriptorSetLayout(allocator, info) catch |err| return toVkResult(err);
|
||||
p_layout.* = (NonDispatchable(DescriptorSetLayout).wrap(allocator, layout) catch |err| return toVkResult(err)).toVkHandle(vk.DescriptorSetLayout);
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub export fn strollCreateFence(p_device: vk.Device, info: *const vk.FenceCreateInfo, callbacks: ?*const vk.AllocationCallbacks, p_fence: *vk.Fence) callconv(vk.vulkan_call_conv) vk.Result {
|
||||
entryPointBeginLogTrace(.vkCreateFence);
|
||||
defer entryPointEndLogTrace();
|
||||
@@ -618,6 +657,28 @@ pub export fn strollDestroyDevice(p_device: vk.Device, callbacks: ?*const vk.All
|
||||
dispatchable.destroy(allocator);
|
||||
}
|
||||
|
||||
pub export fn strollDestroyDescriptorPool(p_device: vk.Device, p_pool: vk.DescriptorPool, callbacks: ?*const vk.AllocationCallbacks) callconv(vk.vulkan_call_conv) void {
|
||||
entryPointBeginLogTrace(.vkDestroyDescriptorPool);
|
||||
defer entryPointEndLogTrace();
|
||||
|
||||
Dispatchable(Device).checkHandleValidity(p_device) catch |err| return errorLogger(err);
|
||||
|
||||
const allocator = VulkanAllocator.init(callbacks, .object).allocator();
|
||||
const non_dispatchable = NonDispatchable(DescriptorPool).fromHandle(p_pool) catch |err| return errorLogger(err);
|
||||
non_dispatchable.intrusiveDestroy(allocator);
|
||||
}
|
||||
|
||||
pub export fn strollDestroyDescriptorSetLayout(p_device: vk.Device, p_layout: vk.DescriptorSetLayout, callbacks: ?*const vk.AllocationCallbacks) callconv(vk.vulkan_call_conv) void {
|
||||
entryPointBeginLogTrace(.vkDestroyDescriptorLayout);
|
||||
defer entryPointEndLogTrace();
|
||||
|
||||
Dispatchable(Device).checkHandleValidity(p_device) catch |err| return errorLogger(err);
|
||||
|
||||
const allocator = VulkanAllocator.init(callbacks, .object).allocator();
|
||||
const non_dispatchable = NonDispatchable(DescriptorSetLayout).fromHandle(p_layout) catch |err| return errorLogger(err);
|
||||
non_dispatchable.intrusiveDestroy(allocator);
|
||||
}
|
||||
|
||||
pub export fn strollDestroyFence(p_device: vk.Device, p_fence: vk.Fence, callbacks: ?*const vk.AllocationCallbacks) callconv(vk.vulkan_call_conv) void {
|
||||
entryPointBeginLogTrace(.vkDestroyFence);
|
||||
defer entryPointEndLogTrace();
|
||||
@@ -662,6 +723,17 @@ pub export fn strollFreeCommandBuffers(p_device: vk.Device, p_pool: vk.CommandPo
|
||||
pool.freeCommandBuffers(cmds[0..count]) catch |err| return errorLogger(err);
|
||||
}
|
||||
|
||||
pub export fn strollFreeDescriptorSets(p_device: vk.Device, p_pool: vk.CommandPool, count: u32, p_sets: [*]const vk.DescriptorSet) callconv(vk.vulkan_call_conv) void {
|
||||
entryPointBeginLogTrace(.vkFreeDescriptorSets);
|
||||
defer entryPointEndLogTrace();
|
||||
|
||||
Dispatchable(Device).checkHandleValidity(p_device) catch |err| return errorLogger(err);
|
||||
|
||||
const pool = NonDispatchable(DescriptorPool).fromHandleObject(p_pool) catch |err| return errorLogger(err);
|
||||
const sets: [*]*Dispatchable(DescriptorSet) = @ptrCast(@constCast(p_sets));
|
||||
pool.freeDescriptorSets(sets[0..count]) catch |err| return errorLogger(err);
|
||||
}
|
||||
|
||||
pub export fn strollFreeMemory(p_device: vk.Device, p_memory: vk.DeviceMemory, callbacks: ?*const vk.AllocationCallbacks) callconv(vk.vulkan_call_conv) void {
|
||||
entryPointBeginLogTrace(.vkFreeMemory);
|
||||
defer entryPointEndLogTrace();
|
||||
|
||||
Reference in New Issue
Block a user