/**************************************************************************
*
* Copyright 2014 Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sub license, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
**************************************************************************/
#include "util/u_tests.h"
#include "util/u_draw_quad.h"
#include "util/u_format.h"
#include "util/u_inlines.h"
#include "util/u_memory.h"
#include "util/u_simple_shaders.h"
#include "util/u_surface.h"
#include "util/u_string.h"
#include "util/u_tile.h"
#include "tgsi/tgsi_strings.h"
#include "tgsi/tgsi_text.h"
#include "cso_cache/cso_context.h"
#include <stdio.h>
#define TOLERANCE 0.01
static struct pipe_resource *
util_create_texture2d(struct pipe_screen *screen, unsigned width,
unsigned height, enum pipe_format format)
{
struct pipe_resource templ = {{0}};
templ.target = PIPE_TEXTURE_2D;
templ.width0 = width;
templ.height0 = height;
templ.depth0 = 1;
templ.array_size = 1;
templ.format = format;
templ.usage = PIPE_USAGE_DEFAULT;
templ.bind = PIPE_BIND_SAMPLER_VIEW |
(util_format_is_depth_or_stencil(format) ?
PIPE_BIND_DEPTH_STENCIL : PIPE_BIND_RENDER_TARGET);
return screen->resource_create(screen, &templ);
}
static void
util_set_framebuffer_cb0(struct cso_context *cso, struct pipe_context *ctx,
struct pipe_resource *tex)
{
struct pipe_surface templ = {{0}}, *surf;
struct pipe_framebuffer_state fb = {0};
templ.format = tex->format;
surf = ctx->create_surface(ctx, tex, &templ);
fb.width = tex->width0;
fb.height = tex->height0;
fb.cbufs[0] = surf;
fb.nr_cbufs = 1;
cso_set_framebuffer(cso, &fb);
pipe_surface_reference(&surf, NULL);
}
static void
util_set_blend_normal(struct cso_context *cso)
{
struct pipe_blend_state blend = {0};
blend.rt[0].colormask = PIPE_MASK_RGBA;
cso_set_blend(cso, &blend);
}
static void
util_set_dsa_disable(struct cso_context *cso)
{
struct pipe_depth_stencil_alpha_state dsa = {{0}};
cso_set_depth_stencil_alpha(cso, &dsa);
}
static void
util_set_rasterizer_normal(struct cso_context *cso)
{
struct pipe_rasterizer_state rs = {0};
rs.half_pixel_center = 1;
rs.bottom_edge_rule = 1;
rs.depth_clip = 1;
cso_set_rasterizer(cso, &rs);
}
static void
util_set_max_viewport(struct cso_context *cso, struct pipe_resource *tex)
{
struct pipe_viewport_state viewport;
viewport.scale[0] = 0.5f * tex->width0;
viewport.scale[1] = 0.5f * tex->height0;
viewport.scale[2] = 1.0f;
viewport.translate[0] = 0.5f * tex->width0;
viewport.translate[1] = 0.5f * tex->height0;
viewport.translate[2] = 0.0f;
cso_set_viewport(cso, &viewport);
}
static void
util_set_interleaved_vertex_elements(struct cso_context *cso,
unsigned num_elements)
{
unsigned i;
struct pipe_vertex_element *velem =
calloc(1, num_elements * sizeof(struct pipe_vertex_element));
for (i = 0; i < num_elements; i++) {
velem[i].src_format = PIPE_FORMAT_R32G32B32A32_FLOAT;
velem[i].src_offset = i * 16;
}
cso_set_vertex_elements(cso, num_elements, velem);
free(velem);
}
static void *
util_set_passthrough_vertex_shader(struct cso_context *cso,
struct pipe_context *ctx,
bool window_space)
{
static const uint vs_attribs[] = {
TGSI_SEMANTIC_POSITION,
TGSI_SEMANTIC_GENERIC
};
static const uint vs_indices[] = {0, 0};
void *vs;
vs = util_make_vertex_passthrough_shader(ctx, 2, vs_attribs, vs_indices,
window_space);
cso_set_vertex_shader_handle(cso, vs);
return vs;
}
static void
util_set_common_states_and_clear(struct cso_context *cso, struct pipe_context *ctx,
struct pipe_resource *cb)
{
static const float clear_color[] = {0.1, 0.1, 0.1, 0.1};
util_set_framebuffer_cb0(cso, ctx, cb);
util_set_blend_normal(cso);
util_set_dsa_disable(cso);
util_set_rasterizer_normal(cso);
util_set_max_viewport(cso, cb);
ctx->clear(ctx, PIPE_CLEAR_COLOR0, (void*)clear_color, 0, 0);
}
static void
util_draw_fullscreen_quad(struct cso_context *cso)
{
static float vertices[] = {
-1, -1, 0, 1, 0, 0, 0, 0,
-1, 1, 0, 1, 0, 1, 0, 0,
1, 1, 0, 1, 1, 1, 0, 0,
1, -1, 0, 1, 1, 0, 0, 0
};
util_set_interleaved_vertex_elements(cso, 2);
util_draw_user_vertex_buffer(cso, vertices, PIPE_PRIM_QUADS, 4, 2);
}
/**
* Probe and test if the rectangle contains the expected color.
*
* If "num_expected_colors" > 1, at least one expected color must match
* the probed color. "expected" should be an array of 4*num_expected_colors
* floats.
*/
static bool
util_probe_rect_rgba_multi(struct pipe_context *ctx, struct pipe_resource *tex,
unsigned offx, unsigned offy, unsigned w,
unsigned h,
const float *expected,
unsigned num_expected_colors)
{
struct pipe_transfer *transfer;
void *map;
float *pixels = malloc(w * h * 4 * sizeof(float));
unsigned x,y,e,c;
bool pass = true;
map = pipe_transfer_map(ctx, tex, 0, 0, PIPE_TRANSFER_READ,
offx, offy, w, h, &transfer);
pipe_get_tile_rgba(transfer, map, 0, 0, w, h, pixels);
pipe_transfer_unmap(ctx, transfer);
for (e = 0; e < num_expected_colors; e++) {
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
float *probe = &pixels[(y*w + x)*4];
for (c = 0; c < 4; c++) {
if (fabs(probe[c] - expected[e*4+c]) >= TOLERANCE) {
if (e < num_expected_colors-1)
goto next_color; /* test the next expected color */
printf("Probe color at (%i,%i), ", offx+x, offy+y);
printf("Expected: %.3f, %.3f, %.3f, %.3f, ",
expected[e*4], expected[e*4+1],
expected[e*4+2], expected[e*4+3]);
printf("Got: %.3f, %.3f, %.3f, %.3f\n",
probe[0], probe[1], probe[2], probe[2]);
pass = false;
goto done;
}
}
}
}
break; /* this color was successful */
next_color:;
}
done:
free(pixels);
return pass;
}
static bool
util_probe_rect_rgba(struct pipe_context *ctx, struct pipe_resource *tex,
unsigned offx, unsigned offy, unsigned w, unsigned h,
const float *expected)
{
return util_probe_rect_rgba_multi(ctx, tex, offx, offy, w, h, expected, 1);
}
enum {
SKIP = -1,
FAIL = 0, /* also "false" */
PASS = 1 /* also "true" */
};
static void
util_report_result_helper(int status, const char *name, ...)
{
char buf[256];
va_list ap;
va_start(ap, name);
util_vsnprintf(buf, sizeof(buf), name, ap);
va_end(ap);
printf("Test(%s) = %s\n", buf,
status == SKIP ? "skip" :
status == PASS ? "pass" : "fail");
}
#define util_report_result(status) util_report_result_helper(status, __func__)
/**
* Test TGSI_PROPERTY_VS_WINDOW_SPACE_POSITION.
*
* The viewport state is set as usual, but it should have no effect.
* Clipping should also be disabled.
*
* POSITION.xyz should already be multiplied by 1/w and POSITION.w should
* contain 1/w. By setting w=0, we can test that POSITION.xyz isn't
* multiplied by 1/w (otherwise nothing would be rendered).
*
* TODO: Whether the value of POSITION.w is correctly interpreted as 1/w
* during perspective interpolation is not tested.
*/
static void
tgsi_vs_window_space_position(struct pipe_context *ctx)
{
struct cso_context *cso;
struct pipe_resource *cb;
void *fs, *vs;
bool pass = true;
static const float red[] = {1, 0, 0, 1};
if (!ctx->screen->get_param(ctx->screen,
PIPE_CAP_TGSI_VS_WINDOW_SPACE_POSITION)) {
util_report_result(SKIP);
return;
}
cso = cso_create_context(ctx, 0);
cb = util_create_texture2d(ctx->screen, 256, 256,
PIPE_FORMAT_R8G8B8A8_UNORM);
util_set_common_states_and_clear(cso, ctx, cb);
/* Fragment shader. */
fs = util_make_fragment_passthrough_shader(ctx, TGSI_SEMANTIC_GENERIC,
TGSI_INTERPOLATE_LINEAR, TRUE);
cso_set_fragment_shader_handle(cso, fs);
/* Vertex shader. */
vs = util_set_passthrough_vertex_shader(cso, ctx, true);
/* Draw. */
{
static float vertices[] = {
0, 0, 0, 0, 1, 0, 0, 1,
0, 256, 0, 0, 1, 0, 0, 1,
256, 256, 0, 0, 1, 0, 0, 1,
256, 0, 0, 0, 1, 0, 0, 1,
};
util_set_interleaved_vertex_elements(cso, 2);
util_draw_user_vertex_buffer(cso, vertices, PIPE_PRIM_QUADS, 4, 2);
}
/* Probe pixels. */
pass = pass && util_probe_rect_rgba(ctx, cb, 0, 0,
cb->width0, cb->height0, red);
/* Cleanup. */
cso_destroy_context(cso);
ctx->delete_vs_state(ctx, vs);
ctx->delete_fs_state(ctx, fs);
pipe_resource_reference(&cb, NULL);
util_report_result(pass);
}
static void
null_sampler_view(struct pipe_context *ctx, unsigned tgsi_tex_target)
{
struct cso_context *cso;
struct pipe_resource *cb;
void *fs, *vs;
bool pass = true;
/* 2 expected colors: */
static const float expected_tex[] = {0, 0, 0, 1,
0, 0, 0, 0};
static const float expected_buf[] = {0, 0, 0, 0};
const float *expected = tgsi_tex_target == TGSI_TEXTURE_BUFFER ?
expected_buf : expected_tex;
unsigned num_expected = tgsi_tex_target == TGSI_TEXTURE_BUFFER ? 1 : 2;
if (tgsi_tex_target == TGSI_TEXTURE_BUFFER &&
!ctx->screen->get_param(ctx->screen, PIPE_CAP_TEXTURE_BUFFER_OBJECTS)) {
util_report_result_helper(SKIP, "%s: %s", __func__,
tgsi_texture_names[tgsi_tex_target]);
return;
}
cso = cso_create_context(ctx, 0);
cb = util_create_texture2d(ctx->screen, 256, 256,
PIPE_FORMAT_R8G8B8A8_UNORM);
util_set_common_states_and_clear(cso, ctx, cb);
ctx->set_sampler_views(ctx, PIPE_SHADER_FRAGMENT, 0, 1, NULL);
/* Fragment shader. */
fs = util_make_fragment_tex_shader(ctx, tgsi_tex_target,
TGSI_INTERPOLATE_LINEAR,
TGSI_RETURN_TYPE_FLOAT,
TGSI_RETURN_TYPE_FLOAT, false, false);
cso_set_fragment_shader_handle(cso, fs);
/* Vertex shader. */
vs = util_set_passthrough_vertex_shader(cso, ctx, false);
util_draw_fullscreen_quad(cso);
/* Probe pixels. */
pass = pass && util_probe_rect_rgba_multi(ctx, cb, 0, 0,
cb->width0, cb->height0, expected,
num_expected);
/* Cleanup. */
cso_destroy_context(cso);
ctx->delete_vs_state(ctx, vs);
ctx->delete_fs_state(ctx, fs);
pipe_resource_reference(&cb, NULL);
util_report_result_helper(pass, "%s: %s", __func__,
tgsi_texture_names[tgsi_tex_target]);
}
void
util_test_constant_buffer(struct pipe_context *ctx,
struct pipe_resource *constbuf)
{
struct cso_context *cso;
struct pipe_resource *cb;
void *fs, *vs;
bool pass = true;
static const float zero[] = {0, 0, 0, 0};
cso = cso_create_context(ctx, 0);
cb = util_create_texture2d(ctx->screen, 256, 256,
PIPE_FORMAT_R8G8B8A8_UNORM);
util_set_common_states_and_clear(cso, ctx, cb);
pipe_set_constant_buffer(ctx, PIPE_SHADER_FRAGMENT, 0, constbuf);
/* Fragment shader. */
{
static const char *text = /* I don't like ureg... */
"FRAG\n"
"DCL CONST[0][0]\n"
"DCL OUT[0], COLOR\n"
"MOV OUT[0], CONST[0][0]\n"
"END\n";
struct tgsi_token tokens[1000];
struct pipe_shader_state state;
if (!tgsi_text_translate(text, tokens, ARRAY_SIZE(tokens))) {
puts("Can't compile a fragment shader.");
util_report_result(FAIL);
return;
}
pipe_shader_state_from_tgsi(&state, tokens);
fs = ctx->create_fs_state(ctx, &state);
cso_set_fragment_shader_handle(cso, fs);
}
/* Vertex shader. */
vs = util_set_passthrough_vertex_shader(cso, ctx, false);
util_draw_fullscreen_quad(cso);
/* Probe pixels. */
pass = pass && util_probe_rect_rgba(ctx, cb, 0, 0, cb->width0,
cb->height0, zero);
/* Cleanup. */
cso_destroy_context(cso);
ctx->delete_vs_state(ctx, vs);
ctx->delete_fs_state(ctx, fs);
pipe_resource_reference(&cb, NULL);
util_report_result(pass);
}
static void
null_fragment_shader(struct pipe_context *ctx)
{
struct cso_context *cso;
struct pipe_resource *cb;
void *vs;
struct pipe_rasterizer_state rs = {0};
struct pipe_query *query;
union pipe_query_result qresult;
cso = cso_create_context(ctx, 0);
cb = util_create_texture2d(ctx->screen, 256, 256,
PIPE_FORMAT_R8G8B8A8_UNORM);
util_set_common_states_and_clear(cso, ctx, cb);
/* No rasterization. */
rs.rasterizer_discard = 1;
cso_set_rasterizer(cso, &rs);
vs = util_set_passthrough_vertex_shader(cso, ctx, false);
query = ctx->create_query(ctx, PIPE_QUERY_PRIMITIVES_GENERATED, 0);
ctx->begin_query(ctx, query);
util_draw_fullscreen_quad(cso);
ctx->end_query(ctx, query);
ctx->get_query_result(ctx, query, true, &qresult);
/* Cleanup. */
cso_destroy_context(cso);
ctx->delete_vs_state(ctx, vs);
ctx->destroy_query(ctx, query);
pipe_resource_reference(&cb, NULL);
/* Check PRIMITIVES_GENERATED. */
util_report_result(qresult.u64 == 2);
}
#if defined(PIPE_OS_LINUX) && defined(HAVE_LIBDRM)
#include <libsync.h>
#else
#define sync_merge(str, fd1, fd2) (-1)
#define sync_wait(fd, timeout) (-1)
#endif
static void
test_sync_file_fences(struct pipe_context *ctx)
{
struct pipe_screen *screen = ctx->screen;
bool pass = true;
if (!screen->get_param(screen, PIPE_CAP_NATIVE_FENCE_FD))
return;
struct cso_context *cso = cso_create_context(ctx, 0);
struct pipe_resource *buf =
pipe_buffer_create(screen, 0, PIPE_USAGE_DEFAULT, 1024 * 1024);
struct pipe_resource *tex =
util_create_texture2d(screen, 4096, 1024, PIPE_FORMAT_R8_UNORM);
struct pipe_fence_handle *buf_fence = NULL, *tex_fence = NULL;
/* Run 2 clears, get fencess. */
uint32_t value = 0;
ctx->clear_buffer(ctx, buf, 0, buf->width0, &value, sizeof(value));
ctx->flush(ctx, &buf_fence, PIPE_FLUSH_FENCE_FD);
struct pipe_box box;
u_box_2d(0, 0, tex->width0, tex->height0, &box);
ctx->clear_texture(ctx, tex, 0, &box, &value);
ctx->flush(ctx, &tex_fence, PIPE_FLUSH_FENCE_FD);
pass = pass && buf_fence && tex_fence;
/* Export fences. */
int buf_fd = screen->fence_get_fd(screen, buf_fence);
int tex_fd = screen->fence_get_fd(screen, tex_fence);
pass = pass && buf_fd >= 0 && tex_fd >= 0;
/* Merge fences. */
int merged_fd = sync_merge("test", buf_fd, tex_fd);
pass = pass && merged_fd >= 0;
/* (Re)import all fences. */
struct pipe_fence_handle *re_buf_fence = NULL, *re_tex_fence = NULL;
struct pipe_fence_handle *merged_fence = NULL;
ctx->create_fence_fd(ctx, &re_buf_fence, buf_fd);
ctx->create_fence_fd(ctx, &re_tex_fence, tex_fd);
ctx->create_fence_fd(ctx, &merged_fence, merged_fd);
pass = pass && re_buf_fence && re_tex_fence && merged_fence;
/* Run another clear after waiting for everything. */
struct pipe_fence_handle *final_fence = NULL;
ctx->fence_server_sync(ctx, merged_fence);
value = 0xff;
ctx->clear_buffer(ctx, buf, 0, buf->width0, &value, sizeof(value));
ctx->flush(ctx, &final_fence, PIPE_FLUSH_FENCE_FD);
pass = pass && final_fence;
/* Wait for the last fence. */
int final_fd = screen->fence_get_fd(screen, final_fence);
pass = pass && final_fd >= 0;
pass = pass && sync_wait(final_fd, -1) == 0;
/* Check that all fences are signalled. */
pass = pass && sync_wait(buf_fd, 0) == 0;
pass = pass && sync_wait(tex_fd, 0) == 0;
pass = pass && sync_wait(merged_fd, 0) == 0;
pass = pass && screen->fence_finish(screen, NULL, buf_fence, 0);
pass = pass && screen->fence_finish(screen, NULL, tex_fence, 0);
pass = pass && screen->fence_finish(screen, NULL, re_buf_fence, 0);
pass = pass && screen->fence_finish(screen, NULL, re_tex_fence, 0);
pass = pass && screen->fence_finish(screen, NULL, merged_fence, 0);
pass = pass && screen->fence_finish(screen, NULL, final_fence, 0);
/* Cleanup. */
#ifndef PIPE_OS_WINDOWS
if (buf_fd >= 0)
close(buf_fd);
if (tex_fd >= 0)
close(tex_fd);
if (merged_fd >= 0)
close(merged_fd);
if (final_fd >= 0)
close(final_fd);
#endif
screen->fence_reference(screen, &buf_fence, NULL);
screen->fence_reference(screen, &tex_fence, NULL);
screen->fence_reference(screen, &re_buf_fence, NULL);
screen->fence_reference(screen, &re_tex_fence, NULL);
screen->fence_reference(screen, &merged_fence, NULL);
screen->fence_reference(screen, &final_fence, NULL);
cso_destroy_context(cso);
pipe_resource_reference(&buf, NULL);
pipe_resource_reference(&tex, NULL);
util_report_result(pass);
}
/**
* Run all tests. This should be run with a clean context after
* context_create.
*/
void
util_run_tests(struct pipe_screen *screen)
{
struct pipe_context *ctx = screen->context_create(screen, NULL, 0);
null_fragment_shader(ctx);
tgsi_vs_window_space_position(ctx);
null_sampler_view(ctx, TGSI_TEXTURE_2D);
null_sampler_view(ctx, TGSI_TEXTURE_BUFFER);
util_test_constant_buffer(ctx, NULL);
test_sync_file_fences(ctx);
ctx->destroy(ctx);
puts("Done. Exiting..");
exit(0);
}