forked from MoonsideGames/Refresh
update API to allow batchable shader params
parent
da9b255dec
commit
2548833525
|
@ -650,6 +650,8 @@ REFRESHAPI void REFRESH_Clear(
|
||||||
* instanceCount: The number of instances that will be drawn.
|
* instanceCount: The number of instances that will be drawn.
|
||||||
* indices: The index buffer to bind for this draw call.
|
* indices: The index buffer to bind for this draw call.
|
||||||
* indexElementSize: The size of the index type for this index buffer.
|
* indexElementSize: The size of the index type for this index buffer.
|
||||||
|
* vertexParamOffset: The offset of the vertex shader param data.
|
||||||
|
* fragmentParamOffset: The offset of the fragment shader param data.
|
||||||
*/
|
*/
|
||||||
REFRESHAPI void REFRESH_DrawInstancedPrimitives(
|
REFRESHAPI void REFRESH_DrawInstancedPrimitives(
|
||||||
REFRESH_Device *device,
|
REFRESH_Device *device,
|
||||||
|
@ -660,7 +662,9 @@ REFRESHAPI void REFRESH_DrawInstancedPrimitives(
|
||||||
uint32_t primitiveCount,
|
uint32_t primitiveCount,
|
||||||
uint32_t instanceCount,
|
uint32_t instanceCount,
|
||||||
REFRESH_Buffer *indices,
|
REFRESH_Buffer *indices,
|
||||||
REFRESH_IndexElementSize indexElementSize
|
REFRESH_IndexElementSize indexElementSize,
|
||||||
|
uint32_t vertexParamOffset,
|
||||||
|
uint32_t fragmentParamOffset
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Draws data from vertex/index buffers.
|
/* Draws data from vertex/index buffers.
|
||||||
|
@ -672,6 +676,8 @@ REFRESHAPI void REFRESH_DrawInstancedPrimitives(
|
||||||
* primitiveCount: The number of primitives to draw.
|
* primitiveCount: The number of primitives to draw.
|
||||||
* indices: The index buffer to bind for this draw call.
|
* indices: The index buffer to bind for this draw call.
|
||||||
* indexElementSize: The size of the index type for this index buffer.
|
* indexElementSize: The size of the index type for this index buffer.
|
||||||
|
* vertexParamOffset: The offset of the vertex shader param data.
|
||||||
|
* fragmentParamOffset: The offset of the fragment shader param data.
|
||||||
*/
|
*/
|
||||||
REFRESHAPI void REFRESH_DrawIndexedPrimitives(
|
REFRESHAPI void REFRESH_DrawIndexedPrimitives(
|
||||||
REFRESH_Device *device,
|
REFRESH_Device *device,
|
||||||
|
@ -681,18 +687,24 @@ REFRESHAPI void REFRESH_DrawIndexedPrimitives(
|
||||||
uint32_t startIndex,
|
uint32_t startIndex,
|
||||||
uint32_t primitiveCount,
|
uint32_t primitiveCount,
|
||||||
REFRESH_Buffer *indices,
|
REFRESH_Buffer *indices,
|
||||||
REFRESH_IndexElementSize indexElementSize
|
REFRESH_IndexElementSize indexElementSize,
|
||||||
|
uint32_t vertexParamOffset,
|
||||||
|
uint32_t fragmentParamOffset
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Draws data from vertex buffers.
|
/* Draws data from vertex buffers.
|
||||||
*
|
*
|
||||||
* vertexStart: The starting offset to read from the vertex buffer.
|
* vertexStart: The starting offset to read from the vertex buffer.
|
||||||
* primitiveCount: The number of primitives to draw.
|
* primitiveCount: The number of primitives to draw.
|
||||||
|
* vertexParamOffset: The offset of the vertex shader param data.
|
||||||
|
* fragmentParamOffset: The offset of the fragment shader param data.
|
||||||
*/
|
*/
|
||||||
REFRESHAPI void REFRESH_DrawPrimitives(
|
REFRESHAPI void REFRESH_DrawPrimitives(
|
||||||
REFRESH_Device *device,
|
REFRESH_Device *device,
|
||||||
uint32_t vertexStart,
|
uint32_t vertexStart,
|
||||||
uint32_t primitiveCount
|
uint32_t primitiveCount,
|
||||||
|
uint32_t vertexParamOffset,
|
||||||
|
uint32_t fragmentParamOffset
|
||||||
);
|
);
|
||||||
|
|
||||||
/* State Creation */
|
/* State Creation */
|
||||||
|
@ -734,7 +746,7 @@ REFRESHAPI REFRESH_ShaderModule* REFRESH_CreateShaderModule(
|
||||||
* height: The height of the texture image.
|
* height: The height of the texture image.
|
||||||
* levelCount: The number of mipmap levels to allocate.
|
* levelCount: The number of mipmap levels to allocate.
|
||||||
* usageFlags: Specifies how the texture will be used.
|
* usageFlags: Specifies how the texture will be used.
|
||||||
*
|
*
|
||||||
* Returns an allocated REFRESH_Texture* object. Note that the contents of
|
* Returns an allocated REFRESH_Texture* object. Note that the contents of
|
||||||
* the texture are undefined until SetData is called.
|
* the texture are undefined until SetData is called.
|
||||||
*/
|
*/
|
||||||
|
@ -775,7 +787,7 @@ REFRESHAPI REFRESH_Texture* REFRESH_CreateTexture3D(
|
||||||
* size: The length of the cube side.
|
* size: The length of the cube side.
|
||||||
* levelCount: The number of mipmap levels to allocate.
|
* levelCount: The number of mipmap levels to allocate.
|
||||||
* usageFlags: Specifies how the texture will be used.
|
* usageFlags: Specifies how the texture will be used.
|
||||||
*
|
*
|
||||||
* Returns an allocated REFRESH_Texture* object. Note that the contents of
|
* Returns an allocated REFRESH_Texture* object. Note that the contents of
|
||||||
* the texture are undefined until SetData is called.
|
* the texture are undefined until SetData is called.
|
||||||
*/
|
*/
|
||||||
|
@ -973,31 +985,33 @@ REFRESHAPI void REFRESH_SetIndexBufferData(
|
||||||
uint32_t dataLength
|
uint32_t dataLength
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Pushes vertex shader params for subsequent draw calls.
|
/* Pushes vertex shader params to the device.
|
||||||
|
* Returns a starting offset value to be used with draw calls.
|
||||||
*
|
*
|
||||||
* NOTE:
|
* NOTE:
|
||||||
* A pipeline must be bound.
|
* A pipeline must be bound.
|
||||||
* Will use the block size of the currently bound vertex shader.
|
* Will use the block size of the currently bound vertex shader.
|
||||||
*
|
*
|
||||||
* data: The client data to write into the buffer.
|
* data: The client data to write into the buffer.
|
||||||
* elementCount: The number of elements from the client buffer to write.
|
* paramBlockCount: The number of param-sized blocks from the client buffer to write.
|
||||||
*/
|
*/
|
||||||
REFRESHAPI void REFRESH_PushVertexShaderParams(
|
REFRESHAPI uint32_t REFRESH_PushVertexShaderParams(
|
||||||
REFRESH_Device *device,
|
REFRESH_Device *device,
|
||||||
void *data,
|
void *data,
|
||||||
uint32_t elementCount
|
uint32_t paramBlockCount
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Pushes fragment shader params for subsequent draw calls.
|
/* Pushes fragment shader params to the device.
|
||||||
|
* Returns a starting offset value to be used with draw calls.
|
||||||
*
|
*
|
||||||
* NOTE:
|
* NOTE:
|
||||||
* A pipeline must be bound.
|
* A pipeline must be bound.
|
||||||
* Will use the block size of the currently bound fragment shader.
|
* Will use the block size of the currently bound fragment shader.
|
||||||
*
|
*
|
||||||
* data: The client data to write into the buffer.
|
* data: The client data to write into the buffer.
|
||||||
* elementCount: The number of elements from the client buffer to write.
|
* paramBlockCount: The number of param-sized blocks from the client buffer to write.
|
||||||
*/
|
*/
|
||||||
REFRESHAPI void REFRESH_PushFragmentShaderParams(
|
REFRESHAPI uint32_t REFRESH_PushFragmentShaderParams(
|
||||||
REFRESH_Device *device,
|
REFRESH_Device *device,
|
||||||
void *data,
|
void *data,
|
||||||
uint32_t elementCount
|
uint32_t elementCount
|
||||||
|
|
|
@ -167,7 +167,9 @@ void REFRESH_DrawIndexedPrimitives(
|
||||||
uint32_t startIndex,
|
uint32_t startIndex,
|
||||||
uint32_t primitiveCount,
|
uint32_t primitiveCount,
|
||||||
REFRESH_Buffer *indices,
|
REFRESH_Buffer *indices,
|
||||||
REFRESH_IndexElementSize indexElementSize
|
REFRESH_IndexElementSize indexElementSize,
|
||||||
|
uint32_t vertexParamOffset,
|
||||||
|
uint32_t fragmentParamOffset
|
||||||
) {
|
) {
|
||||||
NULL_RETURN(device);
|
NULL_RETURN(device);
|
||||||
device->DrawIndexedPrimitives(
|
device->DrawIndexedPrimitives(
|
||||||
|
@ -178,7 +180,9 @@ void REFRESH_DrawIndexedPrimitives(
|
||||||
startIndex,
|
startIndex,
|
||||||
primitiveCount,
|
primitiveCount,
|
||||||
indices,
|
indices,
|
||||||
indexElementSize
|
indexElementSize,
|
||||||
|
vertexParamOffset,
|
||||||
|
fragmentParamOffset
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +195,9 @@ void REFRESH_DrawInstancedPrimitives(
|
||||||
uint32_t primitiveCount,
|
uint32_t primitiveCount,
|
||||||
uint32_t instanceCount,
|
uint32_t instanceCount,
|
||||||
REFRESH_Buffer *indices,
|
REFRESH_Buffer *indices,
|
||||||
REFRESH_IndexElementSize indexElementSize
|
REFRESH_IndexElementSize indexElementSize,
|
||||||
|
uint32_t vertexParamOffset,
|
||||||
|
uint32_t fragmentParamOffset
|
||||||
) {
|
) {
|
||||||
NULL_RETURN(device);
|
NULL_RETURN(device);
|
||||||
device->DrawInstancedPrimitives(
|
device->DrawInstancedPrimitives(
|
||||||
|
@ -203,20 +209,26 @@ void REFRESH_DrawInstancedPrimitives(
|
||||||
primitiveCount,
|
primitiveCount,
|
||||||
instanceCount,
|
instanceCount,
|
||||||
indices,
|
indices,
|
||||||
indexElementSize
|
indexElementSize,
|
||||||
|
vertexParamOffset,
|
||||||
|
fragmentParamOffset
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void REFRESH_DrawPrimitives(
|
void REFRESH_DrawPrimitives(
|
||||||
REFRESH_Device *device,
|
REFRESH_Device *device,
|
||||||
uint32_t vertexStart,
|
uint32_t vertexStart,
|
||||||
uint32_t primitiveCount
|
uint32_t primitiveCount,
|
||||||
|
uint32_t vertexParamOffset,
|
||||||
|
uint32_t fragmentParamOffset
|
||||||
) {
|
) {
|
||||||
NULL_RETURN(device);
|
NULL_RETURN(device);
|
||||||
device->DrawPrimitives(
|
device->DrawPrimitives(
|
||||||
device->driverData,
|
device->driverData,
|
||||||
vertexStart,
|
vertexStart,
|
||||||
primitiveCount
|
primitiveCount,
|
||||||
|
vertexParamOffset,
|
||||||
|
fragmentParamOffset
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,26 +538,26 @@ void REFRESH_SetIndexBufferData(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void REFRESH_PushVertexShaderParams(
|
uint32_t REFRESH_PushVertexShaderParams(
|
||||||
REFRESH_Device *device,
|
REFRESH_Device *device,
|
||||||
void *data,
|
void *data,
|
||||||
uint32_t elementCount
|
uint32_t elementCount
|
||||||
) {
|
) {
|
||||||
NULL_RETURN(device);
|
if (device == NULL) { return 0; }
|
||||||
device->PushVertexShaderParams(
|
return device->PushVertexShaderParams(
|
||||||
device->driverData,
|
device->driverData,
|
||||||
data,
|
data,
|
||||||
elementCount
|
elementCount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void REFRESH_PushFragmentShaderParams(
|
uint32_t REFRESH_PushFragmentShaderParams(
|
||||||
REFRESH_Device *device,
|
REFRESH_Device *device,
|
||||||
void *data,
|
void *data,
|
||||||
uint32_t elementCount
|
uint32_t elementCount
|
||||||
) {
|
) {
|
||||||
NULL_RETURN(device);
|
if (device == NULL) { return 0; }
|
||||||
device->PushFragmentShaderParams(
|
return device->PushFragmentShaderParams(
|
||||||
device->driverData,
|
device->driverData,
|
||||||
data,
|
data,
|
||||||
elementCount
|
elementCount
|
||||||
|
|
|
@ -186,7 +186,9 @@ struct REFRESH_Device
|
||||||
uint32_t primitiveCount,
|
uint32_t primitiveCount,
|
||||||
uint32_t instanceCount,
|
uint32_t instanceCount,
|
||||||
REFRESH_Buffer *indices,
|
REFRESH_Buffer *indices,
|
||||||
REFRESH_IndexElementSize indexElementSize
|
REFRESH_IndexElementSize indexElementSize,
|
||||||
|
uint32_t vertexParamOffset,
|
||||||
|
uint32_t fragmentParamOffset
|
||||||
);
|
);
|
||||||
|
|
||||||
void (*DrawIndexedPrimitives)(
|
void (*DrawIndexedPrimitives)(
|
||||||
|
@ -197,13 +199,17 @@ struct REFRESH_Device
|
||||||
uint32_t startIndex,
|
uint32_t startIndex,
|
||||||
uint32_t primitiveCount,
|
uint32_t primitiveCount,
|
||||||
REFRESH_Buffer *indices,
|
REFRESH_Buffer *indices,
|
||||||
REFRESH_IndexElementSize indexElementSize
|
REFRESH_IndexElementSize indexElementSize,
|
||||||
|
uint32_t vertexParamOffset,
|
||||||
|
uint32_t fragmentParamOffset
|
||||||
);
|
);
|
||||||
|
|
||||||
void (*DrawPrimitives)(
|
void (*DrawPrimitives)(
|
||||||
REFRESH_Renderer *driverData,
|
REFRESH_Renderer *driverData,
|
||||||
uint32_t vertexStart,
|
uint32_t vertexStart,
|
||||||
uint32_t primitiveCount
|
uint32_t primitiveCount,
|
||||||
|
uint32_t vertexParamOffset,
|
||||||
|
uint32_t fragmentParamOffset
|
||||||
);
|
);
|
||||||
|
|
||||||
/* State Creation */
|
/* State Creation */
|
||||||
|
@ -354,13 +360,13 @@ struct REFRESH_Device
|
||||||
uint32_t dataLength
|
uint32_t dataLength
|
||||||
);
|
);
|
||||||
|
|
||||||
void(*PushVertexShaderParams)(
|
uint32_t(*PushVertexShaderParams)(
|
||||||
REFRESH_Renderer *driverData,
|
REFRESH_Renderer *driverData,
|
||||||
void *data,
|
void *data,
|
||||||
uint32_t elementCount
|
uint32_t elementCount
|
||||||
);
|
);
|
||||||
|
|
||||||
void(*PushFragmentShaderParams)(
|
uint32_t(*PushFragmentShaderParams)(
|
||||||
REFRESH_Renderer *driverData,
|
REFRESH_Renderer *driverData,
|
||||||
void *data,
|
void *data,
|
||||||
uint32_t elementCount
|
uint32_t elementCount
|
||||||
|
|
|
@ -3192,7 +3192,9 @@ static void VULKAN_DrawInstancedPrimitives(
|
||||||
uint32_t primitiveCount,
|
uint32_t primitiveCount,
|
||||||
uint32_t instanceCount,
|
uint32_t instanceCount,
|
||||||
REFRESH_Buffer *indices,
|
REFRESH_Buffer *indices,
|
||||||
REFRESH_IndexElementSize indexElementSize
|
REFRESH_IndexElementSize indexElementSize,
|
||||||
|
uint32_t vertexParamOffset,
|
||||||
|
uint32_t fragmentParamOffset
|
||||||
) {
|
) {
|
||||||
VulkanRenderer *renderer = (VulkanRenderer*) driverData;
|
VulkanRenderer *renderer = (VulkanRenderer*) driverData;
|
||||||
VkDescriptorSet descriptorSets[4];
|
VkDescriptorSet descriptorSets[4];
|
||||||
|
@ -3203,8 +3205,8 @@ static void VULKAN_DrawInstancedPrimitives(
|
||||||
descriptorSets[2] = renderer->currentGraphicsPipeline->vertexUBODescriptorSet;
|
descriptorSets[2] = renderer->currentGraphicsPipeline->vertexUBODescriptorSet;
|
||||||
descriptorSets[3] = renderer->currentGraphicsPipeline->fragmentUBODescriptorSet;
|
descriptorSets[3] = renderer->currentGraphicsPipeline->fragmentUBODescriptorSet;
|
||||||
|
|
||||||
dynamicOffsets[0] = renderer->vertexUBOOffset;
|
dynamicOffsets[0] = vertexParamOffset;
|
||||||
dynamicOffsets[1] = renderer->fragmentUBOOffset;
|
dynamicOffsets[1] = fragmentParamOffset;
|
||||||
|
|
||||||
RECORD_CMD(renderer->vkCmdBindDescriptorSets(
|
RECORD_CMD(renderer->vkCmdBindDescriptorSets(
|
||||||
renderer->currentCommandBuffer,
|
renderer->currentCommandBuffer,
|
||||||
|
@ -3238,7 +3240,9 @@ static void VULKAN_DrawIndexedPrimitives(
|
||||||
uint32_t startIndex,
|
uint32_t startIndex,
|
||||||
uint32_t primitiveCount,
|
uint32_t primitiveCount,
|
||||||
REFRESH_Buffer *indices,
|
REFRESH_Buffer *indices,
|
||||||
REFRESH_IndexElementSize indexElementSize
|
REFRESH_IndexElementSize indexElementSize,
|
||||||
|
uint32_t vertexParamOffset,
|
||||||
|
uint32_t fragmentParamOffset
|
||||||
) {
|
) {
|
||||||
VULKAN_DrawInstancedPrimitives(
|
VULKAN_DrawInstancedPrimitives(
|
||||||
driverData,
|
driverData,
|
||||||
|
@ -3249,14 +3253,18 @@ static void VULKAN_DrawIndexedPrimitives(
|
||||||
primitiveCount,
|
primitiveCount,
|
||||||
1,
|
1,
|
||||||
indices,
|
indices,
|
||||||
indexElementSize
|
indexElementSize,
|
||||||
|
vertexParamOffset,
|
||||||
|
fragmentParamOffset
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void VULKAN_DrawPrimitives(
|
static void VULKAN_DrawPrimitives(
|
||||||
REFRESH_Renderer *driverData,
|
REFRESH_Renderer *driverData,
|
||||||
uint32_t vertexStart,
|
uint32_t vertexStart,
|
||||||
uint32_t primitiveCount
|
uint32_t primitiveCount,
|
||||||
|
uint32_t vertexUniformBufferOffset,
|
||||||
|
uint32_t fragmentUniformBufferOffset
|
||||||
) {
|
) {
|
||||||
VulkanRenderer *renderer = (VulkanRenderer*) driverData;
|
VulkanRenderer *renderer = (VulkanRenderer*) driverData;
|
||||||
VkDescriptorSet descriptorSets[4];
|
VkDescriptorSet descriptorSets[4];
|
||||||
|
@ -3267,8 +3275,8 @@ static void VULKAN_DrawPrimitives(
|
||||||
descriptorSets[2] = renderer->currentGraphicsPipeline->vertexUBODescriptorSet;
|
descriptorSets[2] = renderer->currentGraphicsPipeline->vertexUBODescriptorSet;
|
||||||
descriptorSets[3] = renderer->currentGraphicsPipeline->fragmentUBODescriptorSet;
|
descriptorSets[3] = renderer->currentGraphicsPipeline->fragmentUBODescriptorSet;
|
||||||
|
|
||||||
dynamicOffsets[0] = renderer->vertexUBOOffset;
|
dynamicOffsets[0] = vertexUniformBufferOffset;
|
||||||
dynamicOffsets[1] = renderer->fragmentUBOOffset;
|
dynamicOffsets[1] = fragmentUniformBufferOffset;
|
||||||
|
|
||||||
RECORD_CMD(renderer->vkCmdBindDescriptorSets(
|
RECORD_CMD(renderer->vkCmdBindDescriptorSets(
|
||||||
renderer->currentCommandBuffer,
|
renderer->currentCommandBuffer,
|
||||||
|
@ -5663,7 +5671,7 @@ static void VULKAN_SetIndexBufferData(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void VULKAN_PushVertexShaderParams(
|
static uint32_t VULKAN_PushVertexShaderParams(
|
||||||
REFRESH_Renderer *driverData,
|
REFRESH_Renderer *driverData,
|
||||||
void *data,
|
void *data,
|
||||||
uint32_t elementCount
|
uint32_t elementCount
|
||||||
|
@ -5679,7 +5687,7 @@ static void VULKAN_PushVertexShaderParams(
|
||||||
UBO_BUFFER_SIZE * (renderer->frameIndex + 1)
|
UBO_BUFFER_SIZE * (renderer->frameIndex + 1)
|
||||||
) {
|
) {
|
||||||
REFRESH_LogError("Vertex UBO overflow!");
|
REFRESH_LogError("Vertex UBO overflow!");
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
VULKAN_INTERNAL_SetBufferData(
|
VULKAN_INTERNAL_SetBufferData(
|
||||||
|
@ -5689,9 +5697,11 @@ static void VULKAN_PushVertexShaderParams(
|
||||||
data,
|
data,
|
||||||
elementCount * renderer->currentGraphicsPipeline->vertexUBOBlockSize
|
elementCount * renderer->currentGraphicsPipeline->vertexUBOBlockSize
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return renderer->vertexUBOOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void VULKAN_PushFragmentShaderParams(
|
static uint32_t VULKAN_PushFragmentShaderParams(
|
||||||
REFRESH_Renderer *driverData,
|
REFRESH_Renderer *driverData,
|
||||||
void *data,
|
void *data,
|
||||||
uint32_t elementCount
|
uint32_t elementCount
|
||||||
|
@ -5707,7 +5717,7 @@ static void VULKAN_PushFragmentShaderParams(
|
||||||
UBO_BUFFER_SIZE * (renderer->frameIndex + 1)
|
UBO_BUFFER_SIZE * (renderer->frameIndex + 1)
|
||||||
) {
|
) {
|
||||||
REFRESH_LogError("Fragment UBO overflow!");
|
REFRESH_LogError("Fragment UBO overflow!");
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
VULKAN_INTERNAL_SetBufferData(
|
VULKAN_INTERNAL_SetBufferData(
|
||||||
|
@ -5717,6 +5727,8 @@ static void VULKAN_PushFragmentShaderParams(
|
||||||
data,
|
data,
|
||||||
elementCount * renderer->currentGraphicsPipeline->fragmentUBOBlockSize
|
elementCount * renderer->currentGraphicsPipeline->fragmentUBOBlockSize
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return renderer->fragmentUBOOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline uint8_t SamplerDescriptorSetDataEqual(
|
static inline uint8_t SamplerDescriptorSetDataEqual(
|
||||||
|
|
Loading…
Reference in New Issue