tests: Add a test for texture load instructions.

Signed-off-by: Zebediah Figura <zfigura@codeweavers.com>
Signed-off-by: Giovanni Mascellani <gmascellani@codeweavers.com>
Signed-off-by: Matteo Bruni <mbruni@codeweavers.com>
Signed-off-by: Henri Verbeet <hverbeet@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Zebediah Figura 2021-10-03 19:19:08 -05:00 committed by Alexandre Julliard
parent 5f8becada4
commit b1889215ee
5 changed files with 281 additions and 93 deletions

View File

@ -99,6 +99,7 @@ vkd3d_shader_tests = \
tests/swizzle-5.shader_test \ tests/swizzle-5.shader_test \
tests/swizzle-6.shader_test \ tests/swizzle-6.shader_test \
tests/swizzle-7.shader_test \ tests/swizzle-7.shader_test \
tests/texture-load.shader_test \
tests/trigonometry.shader_test \ tests/trigonometry.shader_test \
tests/writemask-assignop-0.shader_test \ tests/writemask-assignop-0.shader_test \
tests/writemask-assignop-1.shader_test \ tests/writemask-assignop-1.shader_test \
@ -299,6 +300,7 @@ XFAIL_TESTS = \
tests/hlsl-vector-indexing-uniform.shader_test \ tests/hlsl-vector-indexing-uniform.shader_test \
tests/math.shader_test \ tests/math.shader_test \
tests/max.shader_test \ tests/max.shader_test \
tests/texture-load.shader_test \
tests/trigonometry.shader_test \ tests/trigonometry.shader_test \
tests/writemask-assignop-1.shader_test tests/writemask-assignop-1.shader_test
endif endif

View File

@ -155,22 +155,6 @@ static void uav_barrier(ID3D12GraphicsCommandList *list, ID3D12Resource *resourc
ID3D12GraphicsCommandList_ResourceBarrier(list, 1, &barrier); ID3D12GraphicsCommandList_ResourceBarrier(list, 1, &barrier);
} }
static void copy_sub_resource_data(const D3D12_MEMCPY_DEST *dst, const D3D12_SUBRESOURCE_DATA *src,
unsigned int row_count, unsigned int slice_count, size_t row_size)
{
const BYTE *src_slice_ptr;
BYTE *dst_slice_ptr;
unsigned int z, y;
for (z = 0; z < slice_count; ++z)
{
dst_slice_ptr = (BYTE *)dst->pData + z * dst->SlicePitch;
src_slice_ptr = (const BYTE*)src->pData + z * src->SlicePitch;
for (y = 0; y < row_count; ++y)
memcpy(dst_slice_ptr + y * dst->RowPitch, src_slice_ptr + y * src->RowPitch, row_size);
}
}
#define upload_buffer_data(a, b, c, d, e, f) upload_buffer_data_(__LINE__, a, b, c, d, e, f) #define upload_buffer_data(a, b, c, d, e, f) upload_buffer_data_(__LINE__, a, b, c, d, e, f)
static void upload_buffer_data_(unsigned int line, ID3D12Resource *buffer, size_t offset, static void upload_buffer_data_(unsigned int line, ID3D12Resource *buffer, size_t offset,
size_t size, const void *data, ID3D12CommandQueue *queue, ID3D12GraphicsCommandList *command_list) size_t size, const void *data, ID3D12CommandQueue *queue, ID3D12GraphicsCommandList *command_list)
@ -196,79 +180,6 @@ static void upload_buffer_data_(unsigned int line, ID3D12Resource *buffer, size_
ID3D12Device_Release(device); ID3D12Device_Release(device);
} }
#define upload_texture_data(a, b, c, d, e) upload_texture_data_(__LINE__, a, b, c, d, e)
static void upload_texture_data_(unsigned int line, ID3D12Resource *texture,
const D3D12_SUBRESOURCE_DATA *data, unsigned int sub_resource_count,
ID3D12CommandQueue *queue, ID3D12GraphicsCommandList *command_list)
{
D3D12_TEXTURE_COPY_LOCATION dst_location, src_location;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT *layouts;
uint64_t *row_sizes, required_size;
D3D12_RESOURCE_DESC resource_desc;
ID3D12Resource *upload_buffer;
D3D12_MEMCPY_DEST dst_data;
ID3D12Device *device;
UINT *row_counts;
unsigned int i;
HRESULT hr;
void *ptr;
layouts = calloc(sub_resource_count, sizeof(*layouts));
ok(layouts, "Failed to allocate memory.\n");
row_counts = calloc(sub_resource_count, sizeof(*row_counts));
ok(row_counts, "Failed to allocate memory.\n");
row_sizes = calloc(sub_resource_count, sizeof(*row_sizes));
ok(row_sizes, "Failed to allocate memory.\n");
resource_desc = ID3D12Resource_GetDesc(texture);
hr = ID3D12Resource_GetDevice(texture, &IID_ID3D12Device, (void **)&device);
ok_(line)(SUCCEEDED(hr), "Failed to get device, hr %#x.\n", hr);
ID3D12Device_GetCopyableFootprints(device, &resource_desc, 0, sub_resource_count,
0, layouts, row_counts, row_sizes, &required_size);
upload_buffer = create_upload_buffer_(line, device, required_size, NULL);
hr = ID3D12Resource_Map(upload_buffer, 0, NULL, (void **)&ptr);
ok_(line)(SUCCEEDED(hr), "Failed to map upload buffer, hr %#x.\n", hr);
for (i = 0; i < sub_resource_count; ++i)
{
dst_data.pData = (BYTE *)ptr + layouts[i].Offset;
dst_data.RowPitch = layouts[i].Footprint.RowPitch;
dst_data.SlicePitch = layouts[i].Footprint.RowPitch * row_counts[i];
copy_sub_resource_data(&dst_data, &data[i],
row_counts[i], layouts[i].Footprint.Depth, row_sizes[i]);
}
ID3D12Resource_Unmap(upload_buffer, 0, NULL);
for (i = 0; i < sub_resource_count; ++i)
{
dst_location.pResource = texture;
dst_location.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
dst_location.SubresourceIndex = i;
src_location.pResource = upload_buffer;
src_location.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
src_location.PlacedFootprint = layouts[i];
ID3D12GraphicsCommandList_CopyTextureRegion(command_list,
&dst_location, 0, 0, 0, &src_location, NULL);
}
hr = ID3D12GraphicsCommandList_Close(command_list);
ok_(line)(SUCCEEDED(hr), "Failed to close command list, hr %#x.\n", hr);
exec_command_list(queue, command_list);
wait_queue_idle(device, queue);
ID3D12Resource_Release(upload_buffer);
ID3D12Device_Release(device);
free(layouts);
free(row_counts);
free(row_sizes);
}
static const DXGI_FORMAT depth_stencil_formats[] = static const DXGI_FORMAT depth_stencil_formats[] =
{ {
DXGI_FORMAT_R32G8X24_TYPELESS, DXGI_FORMAT_R32G8X24_TYPELESS,

View File

@ -697,6 +697,95 @@ static inline ID3D12Resource *create_default_texture3d_(unsigned int line, ID3D1
width, height, depth, miplevel_count, format, flags, initial_state); width, height, depth, miplevel_count, format, flags, initial_state);
} }
static void copy_sub_resource_data(const D3D12_MEMCPY_DEST *dst, const D3D12_SUBRESOURCE_DATA *src,
unsigned int row_count, unsigned int slice_count, size_t row_size)
{
const BYTE *src_slice_ptr;
BYTE *dst_slice_ptr;
unsigned int z, y;
for (z = 0; z < slice_count; ++z)
{
dst_slice_ptr = (BYTE *)dst->pData + z * dst->SlicePitch;
src_slice_ptr = (const BYTE*)src->pData + z * src->SlicePitch;
for (y = 0; y < row_count; ++y)
memcpy(dst_slice_ptr + y * dst->RowPitch, src_slice_ptr + y * src->RowPitch, row_size);
}
}
#define upload_texture_data(a, b, c, d, e) upload_texture_data_(__LINE__, a, b, c, d, e)
static inline void upload_texture_data_(unsigned int line, ID3D12Resource *texture,
const D3D12_SUBRESOURCE_DATA *data, unsigned int sub_resource_count,
ID3D12CommandQueue *queue, ID3D12GraphicsCommandList *command_list)
{
D3D12_TEXTURE_COPY_LOCATION dst_location, src_location;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT *layouts;
uint64_t *row_sizes, required_size;
D3D12_RESOURCE_DESC resource_desc;
ID3D12Resource *upload_buffer;
D3D12_MEMCPY_DEST dst_data;
ID3D12Device *device;
UINT *row_counts;
unsigned int i;
HRESULT hr;
void *ptr;
layouts = calloc(sub_resource_count, sizeof(*layouts));
ok(layouts, "Failed to allocate memory.\n");
row_counts = calloc(sub_resource_count, sizeof(*row_counts));
ok(row_counts, "Failed to allocate memory.\n");
row_sizes = calloc(sub_resource_count, sizeof(*row_sizes));
ok(row_sizes, "Failed to allocate memory.\n");
resource_desc = ID3D12Resource_GetDesc(texture);
hr = ID3D12Resource_GetDevice(texture, &IID_ID3D12Device, (void **)&device);
ok_(line)(SUCCEEDED(hr), "Failed to get device, hr %#x.\n", hr);
ID3D12Device_GetCopyableFootprints(device, &resource_desc, 0, sub_resource_count,
0, layouts, row_counts, row_sizes, &required_size);
upload_buffer = create_upload_buffer_(line, device, required_size, NULL);
hr = ID3D12Resource_Map(upload_buffer, 0, NULL, (void **)&ptr);
ok_(line)(SUCCEEDED(hr), "Failed to map upload buffer, hr %#x.\n", hr);
for (i = 0; i < sub_resource_count; ++i)
{
dst_data.pData = (BYTE *)ptr + layouts[i].Offset;
dst_data.RowPitch = layouts[i].Footprint.RowPitch;
dst_data.SlicePitch = layouts[i].Footprint.RowPitch * row_counts[i];
copy_sub_resource_data(&dst_data, &data[i],
row_counts[i], layouts[i].Footprint.Depth, row_sizes[i]);
}
ID3D12Resource_Unmap(upload_buffer, 0, NULL);
for (i = 0; i < sub_resource_count; ++i)
{
dst_location.pResource = texture;
dst_location.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
dst_location.SubresourceIndex = i;
src_location.pResource = upload_buffer;
src_location.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
src_location.PlacedFootprint = layouts[i];
ID3D12GraphicsCommandList_CopyTextureRegion(command_list,
&dst_location, 0, 0, 0, &src_location, NULL);
}
hr = ID3D12GraphicsCommandList_Close(command_list);
ok_(line)(SUCCEEDED(hr), "Failed to close command list, hr %#x.\n", hr);
exec_command_list(queue, command_list);
wait_queue_idle(device, queue);
ID3D12Resource_Release(upload_buffer);
ID3D12Device_Release(device);
free(layouts);
free(row_counts);
free(row_sizes);
}
static HRESULT create_root_signature(ID3D12Device *device, const D3D12_ROOT_SIGNATURE_DESC *desc, static HRESULT create_root_signature(ID3D12Device *device, const D3D12_ROOT_SIGNATURE_DESC *desc,
ID3D12RootSignature **root_signature) ID3D12RootSignature **root_signature)
{ {

View File

@ -73,6 +73,20 @@ static bool vkd3d_array_reserve(void **elements, size_t *capacity, size_t elemen
return true; return true;
} }
struct texture
{
unsigned int slot;
unsigned int width, height;
uint8_t *data;
size_t data_size, data_capacity;
D3D12_DESCRIPTOR_RANGE descriptor_range;
ID3D12DescriptorHeap *heap;
ID3D12Resource *resource;
unsigned int root_index;
};
struct shader_context struct shader_context
{ {
struct test_context c; struct test_context c;
@ -81,6 +95,9 @@ struct shader_context
uint32_t *uniforms; uint32_t *uniforms;
size_t uniform_count; size_t uniform_count;
struct texture *textures;
size_t texture_count;
}; };
static ID3D10Blob *compile_shader(const char *source, const char *target) static ID3D10Blob *compile_shader(const char *source, const char *target)
@ -99,6 +116,14 @@ static ID3D10Blob *compile_shader(const char *source, const char *target)
return blob; return blob;
} }
static void free_texture(struct texture *texture)
{
ID3D12DescriptorHeap_Release(texture->heap);
ID3D12Resource_Release(texture->resource);
free(texture->data);
memset(texture, 0, sizeof(*texture));
}
enum parse_state enum parse_state
{ {
STATE_NONE, STATE_NONE,
@ -106,6 +131,7 @@ enum parse_state
STATE_PREPROC_INVALID, STATE_PREPROC_INVALID,
STATE_SHADER_INVALID_PIXEL, STATE_SHADER_INVALID_PIXEL,
STATE_SHADER_PIXEL, STATE_SHADER_PIXEL,
STATE_TEXTURE,
STATE_TEST, STATE_TEST,
}; };
@ -124,6 +150,37 @@ static bool match_string(const char *line, const char *token, const char **const
return true; return true;
} }
static void parse_texture_directive(struct texture *texture, const char *line)
{
const char *const orig_line = line;
int ret;
if (match_string(line, "size", &line))
{
ret = sscanf(line, "( %u , %u )", &texture->width, &texture->height);
if (ret < 2)
goto err;
}
else
{
char *rest;
float f;
while ((f = strtof(line, &rest)) || rest != line)
{
vkd3d_array_reserve((void **)&texture->data, &texture->data_capacity, texture->data_size + sizeof(f), 1);
memcpy(texture->data + texture->data_size, &f, sizeof(f));
texture->data_size += sizeof(f);
line = rest;
}
}
return;
err:
fprintf(stderr, "Ignoring malformed line '%s'.\n", orig_line);
}
static void parse_test_directive(struct shader_context *context, const char *line) static void parse_test_directive(struct shader_context *context, const char *line)
{ {
const char *const orig_line = line; const char *const orig_line = line;
@ -133,13 +190,66 @@ static void parse_test_directive(struct shader_context *context, const char *lin
D3D12_SHADER_BYTECODE ps D3D12_SHADER_BYTECODE ps
= {ID3D10Blob_GetBufferPointer(context->ps_code), ID3D10Blob_GetBufferSize(context->ps_code)}; = {ID3D10Blob_GetBufferPointer(context->ps_code), ID3D10Blob_GetBufferSize(context->ps_code)};
ID3D12GraphicsCommandList *command_list = context->c.list; ID3D12GraphicsCommandList *command_list = context->c.list;
D3D12_ROOT_SIGNATURE_DESC root_signature_desc = {0};
D3D12_ROOT_PARAMETER root_params[2], *root_param;
static const float clear_color[4]; static const float clear_color[4];
unsigned int uniform_index;
ID3D12PipelineState *pso; ID3D12PipelineState *pso;
HRESULT hr;
size_t i;
root_signature_desc.NumParameters = 0;
root_signature_desc.pParameters = root_params;
if (context->uniform_count)
{
uniform_index = root_signature_desc.NumParameters++;
root_param = &root_params[uniform_index];
root_param->ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
root_param->Constants.ShaderRegister = 0;
root_param->Constants.RegisterSpace = 0;
root_param->Constants.Num32BitValues = context->uniform_count;
root_param->ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
}
for (i = 0; i < context->texture_count; ++i)
{
struct texture *texture = &context->textures[i];
D3D12_DESCRIPTOR_RANGE *range = &texture->descriptor_range;
D3D12_SUBRESOURCE_DATA resource_data;
texture->root_index = root_signature_desc.NumParameters++;
root_param = &root_params[texture->root_index];
root_param->ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
root_param->DescriptorTable.NumDescriptorRanges = 1;
root_param->DescriptorTable.pDescriptorRanges = range;
root_param->ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
range->RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
range->NumDescriptors = 1;
range->BaseShaderRegister = texture->slot;
range->RegisterSpace = 0;
range->OffsetInDescriptorsFromTableStart = 0;
texture->heap = create_gpu_descriptor_heap(context->c.device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 1);
texture->resource = create_default_texture(context->c.device, texture->width, texture->height,
DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D12_RESOURCE_STATE_COPY_DEST);
resource_data.pData = texture->data;
resource_data.SlicePitch = resource_data.RowPitch = texture->width * sizeof(struct vec4);
upload_texture_data(texture->resource, &resource_data, 1, context->c.queue, command_list);
reset_command_list(command_list, context->c.allocator);
transition_resource_state(command_list, texture->resource, D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
ID3D12Device_CreateShaderResourceView(context->c.device, texture->resource,
NULL, get_cpu_descriptor_handle(&context->c, texture->heap, 0));
}
assert(root_signature_desc.NumParameters <= ARRAY_SIZE(root_params));
if (context->c.root_signature) if (context->c.root_signature)
ID3D12RootSignature_Release(context->c.root_signature); ID3D12RootSignature_Release(context->c.root_signature);
context->c.root_signature = create_32bit_constants_root_signature(context->c.device, hr = create_root_signature(context->c.device, &root_signature_desc, &context->c.root_signature);
0, context->uniform_count, D3D12_SHADER_VISIBILITY_ALL); ok(hr == S_OK, "Failed to create root signature, hr %#x.\n", hr);
pso = create_pipeline_state(context->c.device, context->c.root_signature, pso = create_pipeline_state(context->c.device, context->c.root_signature,
context->c.render_target_desc.Format, NULL, &ps, NULL); context->c.render_target_desc.Format, NULL, &ps, NULL);
@ -147,8 +257,13 @@ static void parse_test_directive(struct shader_context *context, const char *lin
return; return;
ID3D12GraphicsCommandList_SetGraphicsRootSignature(command_list, context->c.root_signature); ID3D12GraphicsCommandList_SetGraphicsRootSignature(command_list, context->c.root_signature);
ID3D12GraphicsCommandList_SetGraphicsRoot32BitConstants(command_list, 0, if (context->uniform_count)
context->uniform_count, context->uniforms, 0); ID3D12GraphicsCommandList_SetGraphicsRoot32BitConstants(command_list, uniform_index,
context->uniform_count, context->uniforms, 0);
for (i = 0; i < context->texture_count; ++i)
ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable(command_list, context->textures[i].root_index,
get_gpu_descriptor_handle(&context->c, context->textures[i].heap, 0));
ID3D12GraphicsCommandList_OMSetRenderTargets(command_list, 1, &context->c.rtv, false, NULL); ID3D12GraphicsCommandList_OMSetRenderTargets(command_list, 1, &context->c.rtv, false, NULL);
ID3D12GraphicsCommandList_RSSetScissorRects(command_list, 1, &context->c.scissor_rect); ID3D12GraphicsCommandList_RSSetScissorRects(command_list, 1, &context->c.scissor_rect);
ID3D12GraphicsCommandList_RSSetViewports(command_list, 1, &context->c.viewport); ID3D12GraphicsCommandList_RSSetViewports(command_list, 1, &context->c.viewport);
@ -293,6 +408,21 @@ err:
fprintf(stderr, "Ignoring malformed line '%s'.\n", orig_line); fprintf(stderr, "Ignoring malformed line '%s'.\n", orig_line);
} }
static struct texture *get_texture(struct shader_context *context, unsigned int slot)
{
struct texture *texture;
size_t i;
for (i = 0; i < context->texture_count; ++i)
{
texture = &context->textures[i];
if (texture->slot == slot)
return texture;
}
return NULL;
}
START_TEST(shader_runner_d3d12) START_TEST(shader_runner_d3d12)
{ {
static const struct test_context_desc desc = static const struct test_context_desc desc =
@ -305,6 +435,7 @@ START_TEST(shader_runner_d3d12)
size_t shader_source_size = 0, shader_source_len = 0; size_t shader_source_size = 0, shader_source_len = 0;
enum parse_state state = STATE_NONE; enum parse_state state = STATE_NONE;
unsigned int i, line_number = 0; unsigned int i, line_number = 0;
struct texture *current_texture;
struct shader_context context; struct shader_context context;
const char *filename = NULL; const char *filename = NULL;
char *shader_source = NULL; char *shader_source = NULL;
@ -351,6 +482,7 @@ START_TEST(shader_runner_d3d12)
{ {
case STATE_NONE: case STATE_NONE:
case STATE_TEST: case STATE_TEST:
case STATE_TEXTURE:
break; break;
case STATE_SHADER_PIXEL: case STATE_SHADER_PIXEL:
@ -439,16 +571,45 @@ START_TEST(shader_runner_d3d12)
if (line[0] == '[') if (line[0] == '[')
{ {
unsigned int index;
if (!strcmp(line, "[pixel shader]\n")) if (!strcmp(line, "[pixel shader]\n"))
{
state = STATE_SHADER_PIXEL; state = STATE_SHADER_PIXEL;
}
else if (!strcmp(line, "[pixel shader fail]\n")) else if (!strcmp(line, "[pixel shader fail]\n"))
{
state = STATE_SHADER_INVALID_PIXEL; state = STATE_SHADER_INVALID_PIXEL;
}
else if (sscanf(line, "[texture %u]\n", &index))
{
state = STATE_TEXTURE;
if ((current_texture = get_texture(&context, index)))
{
free_texture(current_texture);
}
else
{
context.textures = realloc(context.textures,
++context.texture_count * sizeof(*context.textures));
current_texture = &context.textures[context.texture_count - 1];
memset(current_texture, 0, sizeof(*current_texture));
}
current_texture->slot = index;
}
else if (!strcmp(line, "[test]\n")) else if (!strcmp(line, "[test]\n"))
{
state = STATE_TEST; state = STATE_TEST;
}
else if (!strcmp(line, "[preproc]\n")) else if (!strcmp(line, "[preproc]\n"))
{
state = STATE_PREPROC; state = STATE_PREPROC;
}
else if (!strcmp(line, "[preproc fail]\n")) else if (!strcmp(line, "[preproc fail]\n"))
{
state = STATE_PREPROC_INVALID; state = STATE_PREPROC_INVALID;
}
vkd3d_test_set_context("Section %.*s, line %u", strlen(line) - 1, line, line_number); vkd3d_test_set_context("Section %.*s, line %u", strlen(line) - 1, line, line_number);
} }
@ -473,6 +634,10 @@ START_TEST(shader_runner_d3d12)
break; break;
} }
case STATE_TEXTURE:
parse_texture_directive(current_texture, line);
break;
case STATE_TEST: case STATE_TEST:
parse_test_directive(&context, line); parse_test_directive(&context, line);
break; break;
@ -482,6 +647,8 @@ START_TEST(shader_runner_d3d12)
if (context.ps_code) if (context.ps_code)
ID3D10Blob_Release(context.ps_code); ID3D10Blob_Release(context.ps_code);
for (i = 0; i < context.texture_count; ++i)
free_texture(&context.textures[i]);
destroy_test_context(&context.c); destroy_test_context(&context.c);
fclose(f); fclose(f);

View File

@ -0,0 +1,19 @@
[texture 0]
size (2, 2)
0.1 0.2 0.3 0.4 0.5 0.7 0.6 0.8
0.6 0.5 0.2 0.1 0.8 0.0 0.7 1.0
[pixel shader]
Texture2D t;
float4 main(float4 pos : sv_position) : sv_target
{
return t.Load(int3(pos.xy, 0));
}
[test]
draw quad
probe rgba (0, 0) (0.1, 0.2, 0.3, 0.4)
probe rgba (1, 0) (0.5, 0.7, 0.6, 0.8)
probe rgba (0, 1) (0.6, 0.5, 0.2, 0.1)
probe rgba (1, 1) (0.8, 0.0, 0.7, 1.0)