mirror of https://github.com/xemu-project/xemu.git
1429 lines
50 KiB
C
1429 lines
50 KiB
C
/*
|
|
* Geforce NV2A PGRAPH OpenGL Renderer
|
|
*
|
|
* Copyright (c) 2012 espes
|
|
* Copyright (c) 2015 Jannik Vogel
|
|
* Copyright (c) 2018-2024 Matt Borgerson
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "hw/xbox/nv2a/pgraph/pgraph.h"
|
|
#include "ui/xemu-settings.h"
|
|
#include "hw/xbox/nv2a/nv2a_int.h"
|
|
#include "hw/xbox/nv2a/pgraph/swizzle.h"
|
|
#include "debug.h"
|
|
#include "renderer.h"
|
|
|
|
static void surface_download(NV2AState *d, SurfaceBinding *surface, bool force);
|
|
static void surface_download_to_buffer(NV2AState *d, SurfaceBinding *surface,
|
|
bool swizzle, bool flip, bool downscale,
|
|
uint8_t *pixels);
|
|
static void surface_get_dimensions(PGRAPHState *pg, unsigned int *width, unsigned int *height);
|
|
|
|
void pgraph_gl_set_surface_scale_factor(NV2AState *d, unsigned int scale)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
g_config.display.quality.surface_scale = scale < 1 ? 1 : scale;
|
|
|
|
qemu_mutex_lock(&d->pfifo.lock);
|
|
qatomic_set(&d->pfifo.halt, true);
|
|
qemu_mutex_unlock(&d->pfifo.lock);
|
|
|
|
qemu_mutex_lock(&d->pgraph.lock);
|
|
qemu_event_reset(&r->dirty_surfaces_download_complete);
|
|
qatomic_set(&r->download_dirty_surfaces_pending, true);
|
|
qemu_mutex_unlock(&d->pgraph.lock);
|
|
qemu_mutex_lock(&d->pfifo.lock);
|
|
pfifo_kick(d);
|
|
qemu_mutex_unlock(&d->pfifo.lock);
|
|
qemu_event_wait(&r->dirty_surfaces_download_complete);
|
|
|
|
qemu_mutex_lock(&d->pgraph.lock);
|
|
qemu_event_reset(&d->pgraph.flush_complete);
|
|
qatomic_set(&d->pgraph.flush_pending, true);
|
|
qemu_mutex_unlock(&d->pgraph.lock);
|
|
qemu_mutex_lock(&d->pfifo.lock);
|
|
pfifo_kick(d);
|
|
qemu_mutex_unlock(&d->pfifo.lock);
|
|
qemu_event_wait(&d->pgraph.flush_complete);
|
|
|
|
qemu_mutex_lock(&d->pfifo.lock);
|
|
qatomic_set(&d->pfifo.halt, false);
|
|
pfifo_kick(d);
|
|
qemu_mutex_unlock(&d->pfifo.lock);
|
|
}
|
|
|
|
unsigned int pgraph_gl_get_surface_scale_factor(NV2AState *d)
|
|
{
|
|
return d->pgraph.surface_scale_factor;
|
|
}
|
|
|
|
void pgraph_gl_reload_surface_scale_factor(PGRAPHState *pg)
|
|
{
|
|
int factor = g_config.display.quality.surface_scale;
|
|
pg->surface_scale_factor = factor < 1 ? 1 : factor;
|
|
}
|
|
|
|
// FIXME: Move to common
|
|
static bool framebuffer_dirty(PGRAPHState *pg)
|
|
{
|
|
bool shape_changed = memcmp(&pg->surface_shape, &pg->last_surface_shape,
|
|
sizeof(SurfaceShape)) != 0;
|
|
if (!shape_changed || (!pg->surface_shape.color_format
|
|
&& !pg->surface_shape.zeta_format)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void pgraph_gl_set_surface_dirty(PGRAPHState *pg, bool color, bool zeta)
|
|
{
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
NV2A_DPRINTF("pgraph_set_surface_dirty(%d, %d) -- %d %d\n",
|
|
color, zeta,
|
|
pgraph_color_write_enabled(pg), pgraph_zeta_write_enabled(pg));
|
|
/* FIXME: Does this apply to CLEARs too? */
|
|
color = color && pgraph_color_write_enabled(pg);
|
|
zeta = zeta && pgraph_zeta_write_enabled(pg);
|
|
pg->surface_color.draw_dirty |= color;
|
|
pg->surface_zeta.draw_dirty |= zeta;
|
|
|
|
if (r->color_binding) {
|
|
r->color_binding->draw_dirty |= color;
|
|
r->color_binding->frame_time = pg->frame_time;
|
|
r->color_binding->cleared = false;
|
|
|
|
}
|
|
|
|
if (r->zeta_binding) {
|
|
r->zeta_binding->draw_dirty |= zeta;
|
|
r->zeta_binding->frame_time = pg->frame_time;
|
|
r->zeta_binding->cleared = false;
|
|
|
|
}
|
|
}
|
|
|
|
static void init_render_to_texture(PGRAPHState *pg)
|
|
{
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
const char *vs =
|
|
"#version 330\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
" float x = -1.0 + float((gl_VertexID & 1) << 2);\n"
|
|
" float y = -1.0 + float((gl_VertexID & 2) << 1);\n"
|
|
" gl_Position = vec4(x, y, 0, 1);\n"
|
|
"}\n";
|
|
const char *fs =
|
|
"#version 330\n"
|
|
"uniform sampler2D tex;\n"
|
|
"uniform vec2 surface_size;\n"
|
|
"layout(location = 0) out vec4 out_Color;\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
" vec2 texCoord;\n"
|
|
" texCoord.x = gl_FragCoord.x;\n"
|
|
" texCoord.y = (surface_size.y - gl_FragCoord.y)\n"
|
|
" + (textureSize(tex,0).y - surface_size.y);\n"
|
|
" texCoord /= textureSize(tex,0).xy;\n"
|
|
" out_Color.rgba = texture(tex, texCoord);\n"
|
|
"}\n";
|
|
|
|
r->s2t_rndr.prog = pgraph_gl_compile_shader(vs, fs);
|
|
r->s2t_rndr.tex_loc = glGetUniformLocation(r->s2t_rndr.prog, "tex");
|
|
r->s2t_rndr.surface_size_loc = glGetUniformLocation(r->s2t_rndr.prog,
|
|
"surface_size");
|
|
|
|
glGenVertexArrays(1, &r->s2t_rndr.vao);
|
|
glBindVertexArray(r->s2t_rndr.vao);
|
|
glGenBuffers(1, &r->s2t_rndr.vbo);
|
|
glBindBuffer(GL_ARRAY_BUFFER, r->s2t_rndr.vbo);
|
|
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STATIC_DRAW);
|
|
glGenFramebuffers(1, &r->s2t_rndr.fbo);
|
|
}
|
|
|
|
static void finalize_render_to_texture(PGRAPHState *pg)
|
|
{
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
glDeleteProgram(r->s2t_rndr.prog);
|
|
r->s2t_rndr.prog = 0;
|
|
|
|
glDeleteVertexArrays(1, &r->s2t_rndr.vao);
|
|
r->s2t_rndr.vao = 0;
|
|
|
|
glDeleteBuffers(1, &r->s2t_rndr.vbo);
|
|
r->s2t_rndr.vbo = 0;
|
|
|
|
glDeleteFramebuffers(1, &r->s2t_rndr.fbo);
|
|
r->s2t_rndr.fbo = 0;
|
|
}
|
|
|
|
static bool surface_to_texture_can_fastpath(SurfaceBinding *surface,
|
|
TextureShape *shape)
|
|
{
|
|
// FIXME: Better checks/handling on formats and surface-texture compat
|
|
|
|
int surface_fmt = surface->shape.color_format;
|
|
int texture_fmt = shape->color_format;
|
|
|
|
if (!surface->color) {
|
|
// FIXME: Support zeta to color
|
|
return false;
|
|
}
|
|
|
|
switch (surface_fmt) {
|
|
case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_Z1R5G5B5: switch (texture_fmt) {
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X1R5G5B5: return true;
|
|
default: break;
|
|
}
|
|
break;
|
|
case NV097_SET_SURFACE_FORMAT_COLOR_LE_R5G6B5: switch (texture_fmt) {
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R5G6B5: return true;
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R5G6B5: return true;
|
|
default: break;
|
|
}
|
|
break;
|
|
case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_Z8R8G8B8: switch(texture_fmt) {
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8: return true;
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8: return true;
|
|
default: break;
|
|
}
|
|
break;
|
|
case NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8: switch (texture_fmt) {
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8B8G8R8: return true;
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R8G8B8A8: return true;
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8R8G8B8: return true;
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8R8G8B8: return true;
|
|
default: break;
|
|
}
|
|
break;
|
|
default: break;
|
|
}
|
|
|
|
trace_nv2a_pgraph_surface_texture_compat_failed(
|
|
surface_fmt, texture_fmt);
|
|
return false;
|
|
}
|
|
|
|
static void render_surface_to(NV2AState *d, SurfaceBinding *surface,
|
|
int texture_unit, GLuint gl_target,
|
|
GLuint gl_texture, unsigned int width,
|
|
unsigned int height)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
glActiveTexture(GL_TEXTURE0 + texture_unit);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, r->s2t_rndr.fbo);
|
|
|
|
GLenum draw_buffers[1] = { GL_COLOR_ATTACHMENT0 };
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, gl_target,
|
|
gl_texture, 0);
|
|
glDrawBuffers(1, draw_buffers);
|
|
assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
|
|
assert(glGetError() == GL_NO_ERROR);
|
|
|
|
float color[] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
|
glBindTexture(GL_TEXTURE_2D, surface->gl_buffer);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
|
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color);
|
|
|
|
glBindVertexArray(r->s2t_rndr.vao);
|
|
glBindBuffer(GL_ARRAY_BUFFER, r->s2t_rndr.vbo);
|
|
glUseProgram(r->s2t_rndr.prog);
|
|
glProgramUniform1i(r->s2t_rndr.prog, r->s2t_rndr.tex_loc,
|
|
texture_unit);
|
|
glProgramUniform2f(r->s2t_rndr.prog,
|
|
r->s2t_rndr.surface_size_loc, width, height);
|
|
|
|
glViewport(0, 0, width, height);
|
|
glColorMask(true, true, true, true);
|
|
glDisable(GL_DITHER);
|
|
glDisable(GL_SCISSOR_TEST);
|
|
glDisable(GL_BLEND);
|
|
glDisable(GL_STENCIL_TEST);
|
|
glDisable(GL_CULL_FACE);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
glDrawArrays(GL_TRIANGLES, 0, 3);
|
|
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, gl_target, 0,
|
|
0);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, r->gl_framebuffer);
|
|
glBindVertexArray(r->gl_vertex_array);
|
|
glBindTexture(gl_target, gl_texture);
|
|
glUseProgram(
|
|
r->shader_binding ? r->shader_binding->gl_program : 0);
|
|
}
|
|
|
|
static void render_surface_to_texture_slow(NV2AState *d,
|
|
SurfaceBinding *surface,
|
|
TextureBinding *texture,
|
|
TextureShape *texture_shape,
|
|
int texture_unit)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
|
|
const ColorFormatInfo *f = &kelvin_color_format_gl_map[texture_shape->color_format];
|
|
assert(texture_shape->color_format < ARRAY_SIZE(kelvin_color_format_gl_map));
|
|
nv2a_profile_inc_counter(NV2A_PROF_SURF_TO_TEX_FALLBACK);
|
|
|
|
glActiveTexture(GL_TEXTURE0 + texture_unit);
|
|
glBindTexture(texture->gl_target, texture->gl_texture);
|
|
|
|
unsigned int width = surface->width,
|
|
height = surface->height;
|
|
pgraph_apply_scaling_factor(pg, &width, &height);
|
|
|
|
size_t bufsize = width * height * surface->fmt.bytes_per_pixel;
|
|
|
|
uint8_t *buf = g_malloc(bufsize);
|
|
surface_download_to_buffer(d, surface, false, true, false, buf);
|
|
|
|
width = texture_shape->width;
|
|
height = texture_shape->height;
|
|
pgraph_apply_scaling_factor(pg, &width, &height);
|
|
|
|
glTexImage2D(texture->gl_target, 0, f->gl_internal_format, width, height, 0,
|
|
f->gl_format, f->gl_type, buf);
|
|
g_free(buf);
|
|
glBindTexture(texture->gl_target, texture->gl_texture);
|
|
}
|
|
|
|
/* Note: This function is intended to be called before PGRAPH configures GL
|
|
* state for rendering; it will configure GL state here but only restore a
|
|
* couple of items.
|
|
*/
|
|
void pgraph_gl_render_surface_to_texture(NV2AState *d, SurfaceBinding *surface,
|
|
TextureBinding *texture,
|
|
TextureShape *texture_shape,
|
|
int texture_unit)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
const ColorFormatInfo *f =
|
|
&kelvin_color_format_gl_map[texture_shape->color_format];
|
|
assert(texture_shape->color_format < ARRAY_SIZE(kelvin_color_format_gl_map));
|
|
|
|
nv2a_profile_inc_counter(NV2A_PROF_SURF_TO_TEX);
|
|
|
|
if (!surface_to_texture_can_fastpath(surface, texture_shape)) {
|
|
render_surface_to_texture_slow(d, surface, texture,
|
|
texture_shape, texture_unit);
|
|
return;
|
|
}
|
|
|
|
unsigned int width = texture_shape->width, height = texture_shape->height;
|
|
pgraph_apply_scaling_factor(pg, &width, &height);
|
|
|
|
glActiveTexture(GL_TEXTURE0 + texture_unit);
|
|
glBindTexture(texture->gl_target, texture->gl_texture);
|
|
glTexParameteri(texture->gl_target, GL_TEXTURE_BASE_LEVEL, 0);
|
|
glTexParameteri(texture->gl_target, GL_TEXTURE_MAX_LEVEL, 0);
|
|
glTexParameteri(texture->gl_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexImage2D(texture->gl_target, 0, f->gl_internal_format, width, height, 0,
|
|
f->gl_format, f->gl_type, NULL);
|
|
glBindTexture(texture->gl_target, 0);
|
|
render_surface_to(d, surface, texture_unit, texture->gl_target,
|
|
texture->gl_texture, width, height);
|
|
glBindTexture(texture->gl_target, texture->gl_texture);
|
|
glUseProgram(
|
|
r->shader_binding ? r->shader_binding->gl_program : 0);
|
|
}
|
|
|
|
bool pgraph_gl_check_surface_to_texture_compatibility(
|
|
const SurfaceBinding *surface,
|
|
const TextureShape *shape)
|
|
{
|
|
// FIXME: Better checks/handling on formats and surface-texture compat
|
|
|
|
if ((!surface->swizzle && surface->pitch != shape->pitch) ||
|
|
surface->width != shape->width ||
|
|
surface->height != shape->height) {
|
|
return false;
|
|
}
|
|
|
|
int surface_fmt = surface->shape.color_format;
|
|
int texture_fmt = shape->color_format;
|
|
|
|
if (!surface->color) {
|
|
// FIXME: Support zeta to color
|
|
return false;
|
|
}
|
|
|
|
if (shape->cubemap) {
|
|
// FIXME: Support rendering surface to cubemap face
|
|
return false;
|
|
}
|
|
|
|
if (shape->levels > 1) {
|
|
// FIXME: Support rendering surface to mip levels
|
|
return false;
|
|
}
|
|
|
|
switch (surface_fmt) {
|
|
case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_Z1R5G5B5: switch (texture_fmt) {
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X1R5G5B5: return true;
|
|
default: break;
|
|
}
|
|
break;
|
|
case NV097_SET_SURFACE_FORMAT_COLOR_LE_R5G6B5: switch (texture_fmt) {
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R5G6B5: return true;
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R5G6B5: return true;
|
|
default: break;
|
|
}
|
|
break;
|
|
case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_Z8R8G8B8: switch(texture_fmt) {
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8: return true;
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8: return true;
|
|
default: break;
|
|
}
|
|
break;
|
|
case NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8: switch (texture_fmt) {
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8B8G8R8: return true;
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R8G8B8A8: return true;
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8R8G8B8: return true;
|
|
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8R8G8B8: return true;
|
|
default: break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
trace_nv2a_pgraph_surface_texture_compat_failed(
|
|
surface_fmt, texture_fmt);
|
|
return false;
|
|
}
|
|
|
|
static void wait_for_surface_download(SurfaceBinding *e)
|
|
{
|
|
NV2AState *d = g_nv2a;
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
if (qatomic_read(&e->draw_dirty)) {
|
|
qemu_mutex_lock(&d->pfifo.lock);
|
|
qemu_event_reset(&r->downloads_complete);
|
|
qatomic_set(&e->download_pending, true);
|
|
qatomic_set(&r->downloads_pending, true);
|
|
pfifo_kick(d);
|
|
qemu_mutex_unlock(&d->pfifo.lock);
|
|
qemu_event_wait(&r->downloads_complete);
|
|
}
|
|
}
|
|
|
|
static void surface_access_callback(void *opaque, MemoryRegion *mr, hwaddr addr,
|
|
hwaddr len, bool write)
|
|
{
|
|
SurfaceBinding *e = opaque;
|
|
assert(addr >= e->vram_addr);
|
|
hwaddr offset = addr - e->vram_addr;
|
|
assert(offset < e->size);
|
|
|
|
if (qatomic_read(&e->draw_dirty)) {
|
|
trace_nv2a_pgraph_surface_cpu_access(e->vram_addr, offset);
|
|
wait_for_surface_download(e);
|
|
}
|
|
|
|
if (write && !qatomic_read(&e->upload_pending)) {
|
|
trace_nv2a_pgraph_surface_cpu_access(e->vram_addr, offset);
|
|
qatomic_set(&e->upload_pending, true);
|
|
}
|
|
}
|
|
|
|
static SurfaceBinding *surface_put(NV2AState *d, hwaddr addr,
|
|
SurfaceBinding *surface_in)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
assert(pgraph_gl_surface_get(d, addr) == NULL);
|
|
|
|
SurfaceBinding *surface, *next;
|
|
uintptr_t e_end = surface_in->vram_addr + surface_in->size - 1;
|
|
QTAILQ_FOREACH_SAFE(surface, &r->surfaces, entry, next) {
|
|
uintptr_t s_end = surface->vram_addr + surface->size - 1;
|
|
bool overlapping = !(surface->vram_addr > e_end
|
|
|| surface_in->vram_addr > s_end);
|
|
if (overlapping) {
|
|
trace_nv2a_pgraph_surface_evict_overlapping(
|
|
surface->vram_addr, surface->width, surface->height,
|
|
surface->pitch);
|
|
pgraph_gl_surface_download_if_dirty(d, surface);
|
|
pgraph_gl_surface_invalidate(d, surface);
|
|
}
|
|
}
|
|
|
|
SurfaceBinding *surface_out = g_malloc(sizeof(SurfaceBinding));
|
|
assert(surface_out != NULL);
|
|
*surface_out = *surface_in;
|
|
|
|
if (tcg_enabled()) {
|
|
qemu_mutex_unlock(&d->pgraph.lock);
|
|
qemu_mutex_lock_iothread();
|
|
mem_access_callback_insert(qemu_get_cpu(0),
|
|
d->vram, surface_out->vram_addr, surface_out->size,
|
|
&surface_out->access_cb, &surface_access_callback,
|
|
surface_out);
|
|
qemu_mutex_unlock_iothread();
|
|
qemu_mutex_lock(&d->pgraph.lock);
|
|
}
|
|
|
|
QTAILQ_INSERT_TAIL(&r->surfaces, surface_out, entry);
|
|
|
|
return surface_out;
|
|
}
|
|
|
|
SurfaceBinding *pgraph_gl_surface_get(NV2AState *d, hwaddr addr)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
SurfaceBinding *surface;
|
|
QTAILQ_FOREACH (surface, &r->surfaces, entry) {
|
|
if (surface->vram_addr == addr) {
|
|
return surface;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
SurfaceBinding *pgraph_gl_surface_get_within(NV2AState *d, hwaddr addr)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
SurfaceBinding *surface;
|
|
QTAILQ_FOREACH (surface, &r->surfaces, entry) {
|
|
if (addr >= surface->vram_addr &&
|
|
addr < (surface->vram_addr + surface->size)) {
|
|
return surface;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void pgraph_gl_surface_invalidate(NV2AState *d, SurfaceBinding *surface)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
trace_nv2a_pgraph_surface_invalidated(surface->vram_addr);
|
|
|
|
if (surface == r->color_binding) {
|
|
assert(d->pgraph.surface_color.buffer_dirty);
|
|
pgraph_gl_unbind_surface(d, true);
|
|
}
|
|
if (surface == r->zeta_binding) {
|
|
assert(d->pgraph.surface_zeta.buffer_dirty);
|
|
pgraph_gl_unbind_surface(d, false);
|
|
}
|
|
|
|
if (tcg_enabled()) {
|
|
qemu_mutex_unlock(&d->pgraph.lock);
|
|
qemu_mutex_lock_iothread();
|
|
mem_access_callback_remove_by_ref(qemu_get_cpu(0), surface->access_cb);
|
|
qemu_mutex_unlock_iothread();
|
|
qemu_mutex_lock(&d->pgraph.lock);
|
|
}
|
|
|
|
glDeleteTextures(1, &surface->gl_buffer);
|
|
|
|
QTAILQ_REMOVE(&r->surfaces, surface, entry);
|
|
g_free(surface);
|
|
}
|
|
|
|
static void surface_evict_old(NV2AState *d)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
const int surface_age_limit = 5;
|
|
|
|
SurfaceBinding *s, *next;
|
|
QTAILQ_FOREACH_SAFE(s, &r->surfaces, entry, next) {
|
|
int last_used = d->pgraph.frame_time - s->frame_time;
|
|
if (last_used >= surface_age_limit) {
|
|
trace_nv2a_pgraph_surface_evict_reason("old", s->vram_addr);
|
|
pgraph_gl_surface_download_if_dirty(d, s);
|
|
pgraph_gl_surface_invalidate(d, s);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool check_surface_compatibility(SurfaceBinding *s1, SurfaceBinding *s2,
|
|
bool strict)
|
|
{
|
|
bool format_compatible =
|
|
(s1->color == s2->color) &&
|
|
(s1->fmt.gl_attachment == s2->fmt.gl_attachment) &&
|
|
(s1->fmt.gl_internal_format == s2->fmt.gl_internal_format) &&
|
|
(s1->pitch == s2->pitch) &&
|
|
(s1->shape.clip_x <= s2->shape.clip_x) &&
|
|
(s1->shape.clip_y <= s2->shape.clip_y);
|
|
if (!format_compatible) {
|
|
return false;
|
|
}
|
|
|
|
if (!strict) {
|
|
return (s1->width >= s2->width) && (s1->height >= s2->height);
|
|
} else {
|
|
return (s1->width == s2->width) && (s1->height == s2->height);
|
|
}
|
|
}
|
|
|
|
void pgraph_gl_surface_download_if_dirty(NV2AState *d,
|
|
SurfaceBinding *surface)
|
|
{
|
|
if (surface->draw_dirty) {
|
|
surface_download(d, surface, true);
|
|
}
|
|
}
|
|
|
|
static void bind_current_surface(NV2AState *d)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
if (r->color_binding) {
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, r->color_binding->fmt.gl_attachment,
|
|
GL_TEXTURE_2D, r->color_binding->gl_buffer, 0);
|
|
}
|
|
|
|
if (r->zeta_binding) {
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, r->zeta_binding->fmt.gl_attachment,
|
|
GL_TEXTURE_2D, r->zeta_binding->gl_buffer, 0);
|
|
}
|
|
|
|
if (r->color_binding || r->zeta_binding) {
|
|
assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) ==
|
|
GL_FRAMEBUFFER_COMPLETE);
|
|
}
|
|
}
|
|
|
|
static void surface_copy_shrink_row(uint8_t *out, uint8_t *in,
|
|
unsigned int width,
|
|
unsigned int bytes_per_pixel,
|
|
unsigned int factor)
|
|
{
|
|
if (bytes_per_pixel == 4) {
|
|
for (unsigned int x = 0; x < width; x++) {
|
|
*(uint32_t *)out = *(uint32_t *)in;
|
|
out += 4;
|
|
in += 4 * factor;
|
|
}
|
|
} else if (bytes_per_pixel == 2) {
|
|
for (unsigned int x = 0; x < width; x++) {
|
|
*(uint16_t *)out = *(uint16_t *)in;
|
|
out += 2;
|
|
in += 2 * factor;
|
|
}
|
|
} else {
|
|
for (unsigned int x = 0; x < width; x++) {
|
|
memcpy(out, in, bytes_per_pixel);
|
|
out += bytes_per_pixel;
|
|
in += bytes_per_pixel * factor;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void surface_download_to_buffer(NV2AState *d, SurfaceBinding *surface,
|
|
bool swizzle, bool flip, bool downscale,
|
|
uint8_t *pixels)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
|
|
swizzle &= surface->swizzle;
|
|
downscale &= (pg->surface_scale_factor != 1);
|
|
|
|
trace_nv2a_pgraph_surface_download(
|
|
surface->color ? "COLOR" : "ZETA",
|
|
surface->swizzle ? "sz" : "lin", surface->vram_addr,
|
|
surface->width, surface->height, surface->pitch,
|
|
surface->fmt.bytes_per_pixel);
|
|
|
|
/* Bind destination surface to framebuffer */
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
|
0, 0);
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
|
|
0, 0);
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
|
|
GL_TEXTURE_2D, 0, 0);
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, surface->fmt.gl_attachment,
|
|
GL_TEXTURE_2D, surface->gl_buffer, 0);
|
|
|
|
assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
|
|
|
|
/* Read surface into memory */
|
|
uint8_t *gl_read_buf = pixels;
|
|
|
|
uint8_t *swizzle_buf = pixels;
|
|
if (swizzle) {
|
|
/* FIXME: Allocate big buffer up front and re-alloc if necessary.
|
|
* FIXME: Consider swizzle in shader
|
|
*/
|
|
assert(pg->surface_scale_factor == 1 || downscale);
|
|
swizzle_buf = (uint8_t *)g_malloc(surface->size);
|
|
gl_read_buf = swizzle_buf;
|
|
}
|
|
|
|
if (downscale) {
|
|
pg->scale_buf = (uint8_t *)g_realloc(
|
|
pg->scale_buf, pg->surface_scale_factor * pg->surface_scale_factor *
|
|
surface->size);
|
|
gl_read_buf = pg->scale_buf;
|
|
}
|
|
|
|
glo_readpixels(
|
|
surface->fmt.gl_format, surface->fmt.gl_type, surface->fmt.bytes_per_pixel,
|
|
pg->surface_scale_factor * surface->pitch,
|
|
pg->surface_scale_factor * surface->width,
|
|
pg->surface_scale_factor * surface->height, flip, gl_read_buf);
|
|
|
|
/* FIXME: Replace this with a hw accelerated version */
|
|
if (downscale) {
|
|
assert(surface->pitch >= (surface->width * surface->fmt.bytes_per_pixel));
|
|
uint8_t *out = swizzle_buf, *in = pg->scale_buf;
|
|
for (unsigned int y = 0; y < surface->height; y++) {
|
|
surface_copy_shrink_row(out, in, surface->width,
|
|
surface->fmt.bytes_per_pixel,
|
|
pg->surface_scale_factor);
|
|
in += surface->pitch * pg->surface_scale_factor *
|
|
pg->surface_scale_factor;
|
|
out += surface->pitch;
|
|
}
|
|
}
|
|
|
|
if (swizzle) {
|
|
swizzle_rect(swizzle_buf, surface->width, surface->height, pixels,
|
|
surface->pitch, surface->fmt.bytes_per_pixel);
|
|
g_free(swizzle_buf);
|
|
}
|
|
|
|
/* Re-bind original framebuffer target */
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, surface->fmt.gl_attachment,
|
|
GL_TEXTURE_2D, 0, 0);
|
|
bind_current_surface(d);
|
|
}
|
|
|
|
static void surface_download(NV2AState *d, SurfaceBinding *surface, bool force)
|
|
{
|
|
if (!(surface->download_pending || force)) {
|
|
return;
|
|
}
|
|
|
|
/* FIXME: Respect write enable at last TOU? */
|
|
|
|
nv2a_profile_inc_counter(NV2A_PROF_SURF_DOWNLOAD);
|
|
|
|
surface_download_to_buffer(d, surface, true, true, true,
|
|
d->vram_ptr + surface->vram_addr);
|
|
|
|
memory_region_set_client_dirty(d->vram, surface->vram_addr,
|
|
surface->pitch * surface->height,
|
|
DIRTY_MEMORY_VGA);
|
|
memory_region_set_client_dirty(d->vram, surface->vram_addr,
|
|
surface->pitch * surface->height,
|
|
DIRTY_MEMORY_NV2A_TEX);
|
|
|
|
surface->download_pending = false;
|
|
surface->draw_dirty = false;
|
|
}
|
|
|
|
void pgraph_gl_process_pending_downloads(NV2AState *d)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
SurfaceBinding *surface;
|
|
QTAILQ_FOREACH(surface, &r->surfaces, entry) {
|
|
surface_download(d, surface, false);
|
|
}
|
|
|
|
qatomic_set(&r->downloads_pending, false);
|
|
qemu_event_set(&r->downloads_complete);
|
|
}
|
|
|
|
void pgraph_gl_download_dirty_surfaces(NV2AState *d)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
SurfaceBinding *surface;
|
|
QTAILQ_FOREACH(surface, &r->surfaces, entry) {
|
|
pgraph_gl_surface_download_if_dirty(d, surface);
|
|
}
|
|
|
|
qatomic_set(&r->download_dirty_surfaces_pending, false);
|
|
qemu_event_set(&r->dirty_surfaces_download_complete);
|
|
}
|
|
|
|
static void surface_copy_expand_row(uint8_t *out, uint8_t *in,
|
|
unsigned int width,
|
|
unsigned int bytes_per_pixel,
|
|
unsigned int factor)
|
|
{
|
|
if (bytes_per_pixel == 4) {
|
|
for (unsigned int x = 0; x < width; x++) {
|
|
for (unsigned int i = 0; i < factor; i++) {
|
|
*(uint32_t *)out = *(uint32_t *)in;
|
|
out += bytes_per_pixel;
|
|
}
|
|
in += bytes_per_pixel;
|
|
}
|
|
} else if (bytes_per_pixel == 2) {
|
|
for (unsigned int x = 0; x < width; x++) {
|
|
for (unsigned int i = 0; i < factor; i++) {
|
|
*(uint16_t *)out = *(uint16_t *)in;
|
|
out += bytes_per_pixel;
|
|
}
|
|
in += bytes_per_pixel;
|
|
}
|
|
} else {
|
|
for (unsigned int x = 0; x < width; x++) {
|
|
for (unsigned int i = 0; i < factor; i++) {
|
|
memcpy(out, in, bytes_per_pixel);
|
|
out += bytes_per_pixel;
|
|
}
|
|
in += bytes_per_pixel;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void surface_copy_expand(uint8_t *out, uint8_t *in, unsigned int width,
|
|
unsigned int height,
|
|
unsigned int bytes_per_pixel,
|
|
unsigned int factor)
|
|
{
|
|
size_t out_pitch = width * bytes_per_pixel * factor;
|
|
|
|
for (unsigned int y = 0; y < height; y++) {
|
|
surface_copy_expand_row(out, in, width, bytes_per_pixel, factor);
|
|
uint8_t *row_in = out;
|
|
for (unsigned int i = 1; i < factor; i++) {
|
|
out += out_pitch;
|
|
memcpy(out, row_in, out_pitch);
|
|
}
|
|
in += width * bytes_per_pixel;
|
|
out += out_pitch;
|
|
}
|
|
}
|
|
|
|
void pgraph_gl_upload_surface_data(NV2AState *d, SurfaceBinding *surface,
|
|
bool force)
|
|
{
|
|
if (!(surface->upload_pending || force)) {
|
|
return;
|
|
}
|
|
|
|
nv2a_profile_inc_counter(NV2A_PROF_SURF_UPLOAD);
|
|
|
|
trace_nv2a_pgraph_surface_upload(
|
|
surface->color ? "COLOR" : "ZETA",
|
|
surface->swizzle ? "sz" : "lin", surface->vram_addr,
|
|
surface->width, surface->height, surface->pitch,
|
|
surface->fmt.bytes_per_pixel);
|
|
|
|
PGRAPHState *pg = &d->pgraph;
|
|
|
|
surface->upload_pending = false;
|
|
surface->draw_time = pg->draw_time;
|
|
|
|
// FIXME: Don't query GL for texture binding
|
|
GLint last_texture_binding;
|
|
glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture_binding);
|
|
|
|
// FIXME: Replace with FBO to not disturb current state
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
|
0, 0);
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
|
|
0, 0);
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
|
|
GL_TEXTURE_2D, 0, 0);
|
|
|
|
uint8_t *data = d->vram_ptr;
|
|
uint8_t *buf = data + surface->vram_addr;
|
|
|
|
if (surface->swizzle) {
|
|
buf = (uint8_t*)g_malloc(surface->size);
|
|
unswizzle_rect(data + surface->vram_addr,
|
|
surface->width, surface->height,
|
|
buf,
|
|
surface->pitch,
|
|
surface->fmt.bytes_per_pixel);
|
|
}
|
|
|
|
/* FIXME: Replace this flip/scaling */
|
|
|
|
// This is VRAM so we can't do this inplace!
|
|
uint8_t *flipped_buf = (uint8_t *)g_malloc(
|
|
surface->height * surface->width * surface->fmt.bytes_per_pixel);
|
|
unsigned int irow;
|
|
for (irow = 0; irow < surface->height; irow++) {
|
|
memcpy(&flipped_buf[surface->width * (surface->height - irow - 1)
|
|
* surface->fmt.bytes_per_pixel],
|
|
&buf[surface->pitch * irow],
|
|
surface->width * surface->fmt.bytes_per_pixel);
|
|
}
|
|
|
|
uint8_t *gl_read_buf = flipped_buf;
|
|
unsigned int width = surface->width, height = surface->height;
|
|
|
|
if (pg->surface_scale_factor > 1) {
|
|
pgraph_apply_scaling_factor(pg, &width, &height);
|
|
pg->scale_buf = (uint8_t *)g_realloc(
|
|
pg->scale_buf, width * height * surface->fmt.bytes_per_pixel);
|
|
gl_read_buf = pg->scale_buf;
|
|
uint8_t *out = gl_read_buf, *in = flipped_buf;
|
|
surface_copy_expand(out, in, surface->width, surface->height,
|
|
surface->fmt.bytes_per_pixel,
|
|
d->pgraph.surface_scale_factor);
|
|
}
|
|
|
|
int prev_unpack_alignment;
|
|
glGetIntegerv(GL_UNPACK_ALIGNMENT, &prev_unpack_alignment);
|
|
if (unlikely((width * surface->fmt.bytes_per_pixel) % 4 != 0)) {
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
} else {
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
}
|
|
|
|
glBindTexture(GL_TEXTURE_2D, surface->gl_buffer);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, surface->fmt.gl_internal_format, width,
|
|
height, 0, surface->fmt.gl_format, surface->fmt.gl_type,
|
|
gl_read_buf);
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, prev_unpack_alignment);
|
|
g_free(flipped_buf);
|
|
if (surface->swizzle) {
|
|
g_free(buf);
|
|
}
|
|
|
|
// Rebind previous framebuffer binding
|
|
glBindTexture(GL_TEXTURE_2D, last_texture_binding);
|
|
|
|
bind_current_surface(d);
|
|
}
|
|
|
|
static void compare_surfaces(SurfaceBinding *s1, SurfaceBinding *s2)
|
|
{
|
|
#define DO_CMP(fld) \
|
|
if (s1->fld != s2->fld) \
|
|
trace_nv2a_pgraph_surface_compare_mismatch( \
|
|
#fld, (long int)s1->fld, (long int)s2->fld);
|
|
DO_CMP(shape.clip_x)
|
|
DO_CMP(shape.clip_width)
|
|
DO_CMP(shape.clip_y)
|
|
DO_CMP(shape.clip_height)
|
|
DO_CMP(gl_buffer)
|
|
DO_CMP(fmt.bytes_per_pixel)
|
|
DO_CMP(fmt.gl_attachment)
|
|
DO_CMP(fmt.gl_internal_format)
|
|
DO_CMP(fmt.gl_format)
|
|
DO_CMP(fmt.gl_type)
|
|
DO_CMP(color)
|
|
DO_CMP(swizzle)
|
|
DO_CMP(vram_addr)
|
|
DO_CMP(width)
|
|
DO_CMP(height)
|
|
DO_CMP(pitch)
|
|
DO_CMP(size)
|
|
DO_CMP(dma_addr)
|
|
DO_CMP(dma_len)
|
|
DO_CMP(frame_time)
|
|
DO_CMP(draw_time)
|
|
#undef DO_CMP
|
|
}
|
|
|
|
static void populate_surface_binding_entry_sized(NV2AState *d, bool color,
|
|
unsigned int width,
|
|
unsigned int height,
|
|
SurfaceBinding *entry)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
Surface *surface;
|
|
hwaddr dma_address;
|
|
SurfaceFormatInfo fmt;
|
|
|
|
if (color) {
|
|
surface = &pg->surface_color;
|
|
dma_address = pg->dma_color;
|
|
assert(pg->surface_shape.color_format != 0);
|
|
assert(pg->surface_shape.color_format <
|
|
ARRAY_SIZE(kelvin_surface_color_format_gl_map));
|
|
fmt = kelvin_surface_color_format_gl_map[pg->surface_shape.color_format];
|
|
if (fmt.bytes_per_pixel == 0) {
|
|
fprintf(stderr, "nv2a: unimplemented color surface format 0x%x\n",
|
|
pg->surface_shape.color_format);
|
|
abort();
|
|
}
|
|
} else {
|
|
surface = &pg->surface_zeta;
|
|
dma_address = pg->dma_zeta;
|
|
assert(pg->surface_shape.zeta_format != 0);
|
|
assert(pg->surface_shape.zeta_format <
|
|
ARRAY_SIZE(kelvin_surface_zeta_float_format_gl_map));
|
|
const SurfaceFormatInfo *map =
|
|
pg->surface_shape.z_format ? kelvin_surface_zeta_float_format_gl_map :
|
|
kelvin_surface_zeta_fixed_format_gl_map;
|
|
fmt = map[pg->surface_shape.zeta_format];
|
|
}
|
|
|
|
DMAObject dma = nv_dma_load(d, dma_address);
|
|
/* There's a bunch of bugs that could cause us to hit this function
|
|
* at the wrong time and get a invalid dma object.
|
|
* Check that it's sane. */
|
|
assert(dma.dma_class == NV_DMA_IN_MEMORY_CLASS);
|
|
// assert(dma.address + surface->offset != 0);
|
|
assert(surface->offset <= dma.limit);
|
|
assert(surface->offset + surface->pitch * height <= dma.limit + 1);
|
|
assert(surface->pitch % fmt.bytes_per_pixel == 0);
|
|
assert((dma.address & ~0x07FFFFFF) == 0);
|
|
|
|
entry->shape = (color || !r->color_binding) ? pg->surface_shape :
|
|
r->color_binding->shape;
|
|
entry->gl_buffer = 0;
|
|
entry->fmt = fmt;
|
|
entry->color = color;
|
|
entry->swizzle =
|
|
(pg->surface_type == NV097_SET_SURFACE_FORMAT_TYPE_SWIZZLE);
|
|
entry->vram_addr = dma.address + surface->offset;
|
|
entry->width = width;
|
|
entry->height = height;
|
|
entry->pitch = surface->pitch;
|
|
entry->size = height * MAX(surface->pitch, width * fmt.bytes_per_pixel);
|
|
entry->upload_pending = true;
|
|
entry->download_pending = false;
|
|
entry->draw_dirty = false;
|
|
entry->dma_addr = dma.address;
|
|
entry->dma_len = dma.limit;
|
|
entry->frame_time = pg->frame_time;
|
|
entry->draw_time = pg->draw_time;
|
|
entry->cleared = false;
|
|
}
|
|
|
|
static void populate_surface_binding_entry(NV2AState *d, bool color,
|
|
SurfaceBinding *entry)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
unsigned int width, height;
|
|
|
|
if (color || !r->color_binding) {
|
|
surface_get_dimensions(pg, &width, &height);
|
|
pgraph_apply_anti_aliasing_factor(pg, &width, &height);
|
|
|
|
/* Since we determine surface dimensions based on the clipping
|
|
* rectangle, make sure to include the surface offset as well.
|
|
*/
|
|
if (pg->surface_type != NV097_SET_SURFACE_FORMAT_TYPE_SWIZZLE) {
|
|
width += pg->surface_shape.clip_x;
|
|
height += pg->surface_shape.clip_y;
|
|
}
|
|
} else {
|
|
width = r->color_binding->width;
|
|
height = r->color_binding->height;
|
|
}
|
|
|
|
populate_surface_binding_entry_sized(d, color, width, height, entry);
|
|
}
|
|
|
|
static void update_surface_part(NV2AState *d, bool upload, bool color)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
SurfaceBinding entry;
|
|
populate_surface_binding_entry(d, color, &entry);
|
|
|
|
Surface *surface = color ? &pg->surface_color : &pg->surface_zeta;
|
|
|
|
bool mem_dirty = !tcg_enabled() && memory_region_test_and_clear_dirty(
|
|
d->vram, entry.vram_addr, entry.size,
|
|
DIRTY_MEMORY_NV2A);
|
|
|
|
if (upload && (surface->buffer_dirty || mem_dirty)) {
|
|
pgraph_gl_unbind_surface(d, color);
|
|
|
|
SurfaceBinding *found = pgraph_gl_surface_get(d, entry.vram_addr);
|
|
if (found != NULL) {
|
|
/* FIXME: Support same color/zeta surface target? In the mean time,
|
|
* if the surface we just found is currently bound, just unbind it.
|
|
*/
|
|
SurfaceBinding *other = (color ? r->zeta_binding
|
|
: r->color_binding);
|
|
if (found == other) {
|
|
NV2A_UNIMPLEMENTED("Same color & zeta surface offset");
|
|
pgraph_gl_unbind_surface(d, !color);
|
|
}
|
|
}
|
|
|
|
trace_nv2a_pgraph_surface_target(
|
|
color ? "COLOR" : "ZETA", entry.vram_addr,
|
|
entry.swizzle ? "sz" : "ln",
|
|
pg->surface_shape.anti_aliasing,
|
|
pg->surface_shape.clip_x,
|
|
pg->surface_shape.clip_width, pg->surface_shape.clip_y,
|
|
pg->surface_shape.clip_height);
|
|
|
|
bool should_create = true;
|
|
|
|
if (found != NULL) {
|
|
bool is_compatible =
|
|
check_surface_compatibility(found, &entry, false);
|
|
|
|
#define TRACE_ARGS found->vram_addr, found->width, found->height, \
|
|
found->swizzle ? "sz" : "ln", \
|
|
found->shape.anti_aliasing, found->shape.clip_x, \
|
|
found->shape.clip_width, found->shape.clip_y, \
|
|
found->shape.clip_height, found->pitch
|
|
if (found->color) {
|
|
trace_nv2a_pgraph_surface_match_color(TRACE_ARGS);
|
|
} else {
|
|
trace_nv2a_pgraph_surface_match_zeta(TRACE_ARGS);
|
|
}
|
|
#undef TRACE_ARGS
|
|
|
|
assert(!(entry.swizzle && pg->clearing));
|
|
|
|
if (found->swizzle != entry.swizzle) {
|
|
/* Clears should only be done on linear surfaces. Avoid
|
|
* synchronization by allowing (1) a surface marked swizzled to
|
|
* be cleared under the assumption the entire surface is
|
|
* destined to be cleared and (2) a fully cleared linear surface
|
|
* to be marked swizzled. Strictly match size to avoid
|
|
* pathological cases.
|
|
*/
|
|
is_compatible &= (pg->clearing || found->cleared) &&
|
|
check_surface_compatibility(found, &entry, true);
|
|
if (is_compatible) {
|
|
trace_nv2a_pgraph_surface_migrate_type(
|
|
entry.swizzle ? "swizzled" : "linear");
|
|
}
|
|
}
|
|
|
|
if (is_compatible && color &&
|
|
!check_surface_compatibility(found, &entry, true)) {
|
|
SurfaceBinding zeta_entry;
|
|
populate_surface_binding_entry_sized(
|
|
d, !color, found->width, found->height, &zeta_entry);
|
|
hwaddr color_end = found->vram_addr + found->size;
|
|
hwaddr zeta_end = zeta_entry.vram_addr + zeta_entry.size;
|
|
is_compatible &= found->vram_addr >= zeta_end ||
|
|
zeta_entry.vram_addr >= color_end;
|
|
}
|
|
|
|
if (is_compatible && !color && r->color_binding) {
|
|
is_compatible &= (found->width == r->color_binding->width) &&
|
|
(found->height == r->color_binding->height);
|
|
}
|
|
|
|
if (is_compatible) {
|
|
/* FIXME: Refactor */
|
|
pg->surface_binding_dim.width = found->width;
|
|
pg->surface_binding_dim.clip_x = found->shape.clip_x;
|
|
pg->surface_binding_dim.clip_width = found->shape.clip_width;
|
|
pg->surface_binding_dim.height = found->height;
|
|
pg->surface_binding_dim.clip_y = found->shape.clip_y;
|
|
pg->surface_binding_dim.clip_height = found->shape.clip_height;
|
|
found->upload_pending |= mem_dirty;
|
|
pg->surface_zeta.buffer_dirty |= color;
|
|
should_create = false;
|
|
} else {
|
|
trace_nv2a_pgraph_surface_evict_reason(
|
|
"incompatible", found->vram_addr);
|
|
compare_surfaces(found, &entry);
|
|
pgraph_gl_surface_download_if_dirty(d, found);
|
|
pgraph_gl_surface_invalidate(d, found);
|
|
}
|
|
}
|
|
|
|
if (should_create) {
|
|
glGenTextures(1, &entry.gl_buffer);
|
|
glBindTexture(GL_TEXTURE_2D, entry.gl_buffer);
|
|
NV2A_GL_DLABEL(GL_TEXTURE, entry.gl_buffer,
|
|
"%s format: %0X, width: %d, height: %d "
|
|
"(addr %" HWADDR_PRIx ")",
|
|
color ? "color" : "zeta",
|
|
color ? pg->surface_shape.color_format
|
|
: pg->surface_shape.zeta_format,
|
|
entry.width, entry.height, surface->offset);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
unsigned int width = entry.width, height = entry.height;
|
|
pgraph_apply_scaling_factor(pg, &width, &height);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, entry.fmt.gl_internal_format, width,
|
|
height, 0, entry.fmt.gl_format, entry.fmt.gl_type,
|
|
NULL);
|
|
found = surface_put(d, entry.vram_addr, &entry);
|
|
|
|
/* FIXME: Refactor */
|
|
pg->surface_binding_dim.width = entry.width;
|
|
pg->surface_binding_dim.clip_x = entry.shape.clip_x;
|
|
pg->surface_binding_dim.clip_width = entry.shape.clip_width;
|
|
pg->surface_binding_dim.height = entry.height;
|
|
pg->surface_binding_dim.clip_y = entry.shape.clip_y;
|
|
pg->surface_binding_dim.clip_height = entry.shape.clip_height;
|
|
|
|
if (color && r->zeta_binding && (r->zeta_binding->width != entry.width || r->zeta_binding->height != entry.height)) {
|
|
pg->surface_zeta.buffer_dirty = true;
|
|
}
|
|
}
|
|
|
|
#define TRACE_ARGS found->vram_addr, found->width, found->height, \
|
|
found->swizzle ? "sz" : "ln", found->shape.anti_aliasing, \
|
|
found->shape.clip_x, found->shape.clip_width, \
|
|
found->shape.clip_y, found->shape.clip_height, found->pitch
|
|
|
|
if (color) {
|
|
if (should_create) {
|
|
trace_nv2a_pgraph_surface_create_color(TRACE_ARGS);
|
|
} else {
|
|
trace_nv2a_pgraph_surface_hit_color(TRACE_ARGS);
|
|
}
|
|
|
|
r->color_binding = found;
|
|
} else {
|
|
if (should_create) {
|
|
trace_nv2a_pgraph_surface_create_zeta(TRACE_ARGS);
|
|
} else {
|
|
trace_nv2a_pgraph_surface_hit_zeta(TRACE_ARGS);
|
|
}
|
|
r->zeta_binding = found;
|
|
}
|
|
#undef TRACE_ARGS
|
|
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, entry.fmt.gl_attachment,
|
|
GL_TEXTURE_2D, found->gl_buffer, 0);
|
|
assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) ==
|
|
GL_FRAMEBUFFER_COMPLETE);
|
|
|
|
surface->buffer_dirty = false;
|
|
}
|
|
|
|
if (!upload && surface->draw_dirty) {
|
|
if (!tcg_enabled()) {
|
|
/* FIXME: Cannot monitor for reads/writes; flush now */
|
|
surface_download(d,
|
|
color ? r->color_binding :
|
|
r->zeta_binding,
|
|
true);
|
|
}
|
|
|
|
surface->write_enabled_cache = false;
|
|
surface->draw_dirty = false;
|
|
}
|
|
}
|
|
|
|
void pgraph_gl_unbind_surface(NV2AState *d, bool color)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
if (color) {
|
|
if (r->color_binding) {
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER,
|
|
GL_COLOR_ATTACHMENT0,
|
|
GL_TEXTURE_2D, 0, 0);
|
|
r->color_binding = NULL;
|
|
}
|
|
} else {
|
|
if (r->zeta_binding) {
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER,
|
|
GL_DEPTH_ATTACHMENT,
|
|
GL_TEXTURE_2D, 0, 0);
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER,
|
|
GL_DEPTH_STENCIL_ATTACHMENT,
|
|
GL_TEXTURE_2D, 0, 0);
|
|
r->zeta_binding = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void pgraph_gl_surface_update(NV2AState *d, bool upload, bool color_write,
|
|
bool zeta_write)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
pg->surface_shape.z_format =
|
|
GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER),
|
|
NV_PGRAPH_SETUPRASTER_Z_FORMAT);
|
|
|
|
color_write = color_write &&
|
|
(pg->clearing || pgraph_color_write_enabled(pg));
|
|
zeta_write = zeta_write && (pg->clearing || pgraph_zeta_write_enabled(pg));
|
|
|
|
if (upload) {
|
|
bool fb_dirty = framebuffer_dirty(pg);
|
|
if (fb_dirty) {
|
|
memcpy(&pg->last_surface_shape, &pg->surface_shape,
|
|
sizeof(SurfaceShape));
|
|
pg->surface_color.buffer_dirty = true;
|
|
pg->surface_zeta.buffer_dirty = true;
|
|
}
|
|
|
|
if (pg->surface_color.buffer_dirty) {
|
|
pgraph_gl_unbind_surface(d, true);
|
|
}
|
|
|
|
if (color_write) {
|
|
update_surface_part(d, true, true);
|
|
}
|
|
|
|
if (pg->surface_zeta.buffer_dirty) {
|
|
pgraph_gl_unbind_surface(d, false);
|
|
}
|
|
|
|
if (zeta_write) {
|
|
update_surface_part(d, true, false);
|
|
}
|
|
} else {
|
|
if ((color_write || pg->surface_color.write_enabled_cache)
|
|
&& pg->surface_color.draw_dirty) {
|
|
update_surface_part(d, false, true);
|
|
}
|
|
if ((zeta_write || pg->surface_zeta.write_enabled_cache)
|
|
&& pg->surface_zeta.draw_dirty) {
|
|
update_surface_part(d, false, false);
|
|
}
|
|
}
|
|
|
|
if (upload) {
|
|
pg->draw_time++;
|
|
}
|
|
|
|
bool swizzle = (pg->surface_type == NV097_SET_SURFACE_FORMAT_TYPE_SWIZZLE);
|
|
|
|
if (r->color_binding) {
|
|
r->color_binding->frame_time = pg->frame_time;
|
|
if (upload) {
|
|
pgraph_gl_upload_surface_data(d, r->color_binding, false);
|
|
r->color_binding->draw_time = pg->draw_time;
|
|
r->color_binding->swizzle = swizzle;
|
|
}
|
|
}
|
|
|
|
if (r->zeta_binding) {
|
|
r->zeta_binding->frame_time = pg->frame_time;
|
|
if (upload) {
|
|
pgraph_gl_upload_surface_data(d, r->zeta_binding, false);
|
|
r->zeta_binding->draw_time = pg->draw_time;
|
|
r->zeta_binding->swizzle = swizzle;
|
|
}
|
|
}
|
|
|
|
// Sanity check color and zeta dimensions match
|
|
if (r->color_binding && r->zeta_binding) {
|
|
assert((r->color_binding->width == r->zeta_binding->width)
|
|
&& (r->color_binding->height == r->zeta_binding->height));
|
|
}
|
|
|
|
surface_evict_old(d);
|
|
}
|
|
|
|
// FIXME: Move to common
|
|
static void surface_get_dimensions(PGRAPHState *pg, unsigned int *width,
|
|
unsigned int *height)
|
|
{
|
|
bool swizzle = (pg->surface_type == NV097_SET_SURFACE_FORMAT_TYPE_SWIZZLE);
|
|
if (swizzle) {
|
|
*width = 1 << pg->surface_shape.log_width;
|
|
*height = 1 << pg->surface_shape.log_height;
|
|
} else {
|
|
*width = pg->surface_shape.clip_width;
|
|
*height = pg->surface_shape.clip_height;
|
|
}
|
|
}
|
|
|
|
void pgraph_gl_init_surfaces(PGRAPHState *pg)
|
|
{
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
pgraph_gl_reload_surface_scale_factor(pg);
|
|
glGenFramebuffers(1, &r->gl_framebuffer);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, r->gl_framebuffer);
|
|
QTAILQ_INIT(&r->surfaces);
|
|
r->downloads_pending = false;
|
|
qemu_event_init(&r->downloads_complete, false);
|
|
qemu_event_init(&r->dirty_surfaces_download_complete, false);
|
|
|
|
init_render_to_texture(pg);
|
|
}
|
|
|
|
static void flush_surfaces(NV2AState *d)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
/* Clear last surface shape to force recreation of buffers at next draw */
|
|
pg->surface_color.draw_dirty = false;
|
|
pg->surface_zeta.draw_dirty = false;
|
|
memset(&pg->last_surface_shape, 0, sizeof(pg->last_surface_shape));
|
|
pgraph_gl_unbind_surface(d, true);
|
|
pgraph_gl_unbind_surface(d, false);
|
|
|
|
SurfaceBinding *s, *next;
|
|
QTAILQ_FOREACH_SAFE(s, &r->surfaces, entry, next) {
|
|
// FIXME: We should download all surfaces to ram, but need to
|
|
// investigate corruption issue
|
|
// pgraph_gl_surface_download_if_dirty(d, s);
|
|
pgraph_gl_surface_invalidate(d, s);
|
|
}
|
|
}
|
|
|
|
void pgraph_gl_finalize_surfaces(PGRAPHState *pg)
|
|
{
|
|
NV2AState *d = container_of(pg, NV2AState, pgraph);
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
flush_surfaces(d);
|
|
glDeleteFramebuffers(1, &r->gl_framebuffer);
|
|
r->gl_framebuffer = 0;
|
|
|
|
finalize_render_to_texture(pg);
|
|
}
|
|
|
|
void pgraph_gl_surface_flush(NV2AState *d)
|
|
{
|
|
PGRAPHState *pg = &d->pgraph;
|
|
PGRAPHGLState *r = pg->gl_renderer_state;
|
|
|
|
bool update_surface = (r->color_binding || r->zeta_binding);
|
|
|
|
flush_surfaces(d);
|
|
|
|
pgraph_gl_reload_surface_scale_factor(pg);
|
|
|
|
if (update_surface) {
|
|
pgraph_gl_surface_update(d, true, true, true);
|
|
}
|
|
}
|