A texture cache that never invalidates.

This commit is contained in:
Ben Vanik 2015-01-03 01:05:56 -08:00
parent 79179c28ef
commit 718762746a
8 changed files with 303 additions and 141 deletions

View File

@ -198,12 +198,14 @@ LRESULT Win32Control::WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam) { LPARAM lParam) {
if (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) { if (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) {
if (HandleMouse(message, wParam, lParam)) { if (HandleMouse(message, wParam, lParam)) {
SetFocus(hwnd_);
return 0; return 0;
} else { } else {
return DefWindowProc(hWnd, message, wParam, lParam); return DefWindowProc(hWnd, message, wParam, lParam);
} }
} else if (message >= WM_KEYFIRST && message <= WM_KEYLAST) { } else if (message >= WM_KEYFIRST && message <= WM_KEYLAST) {
if (HandleKeyboard(message, wParam, lParam)) { if (HandleKeyboard(message, wParam, lParam)) {
SetFocus(hwnd_);
return 0; return 0;
} else { } else {
return DefWindowProc(hWnd, message, wParam, lParam); return DefWindowProc(hWnd, message, wParam, lParam);

View File

@ -169,7 +169,7 @@ bool CommandProcessor::SetupGL() {
} }
// Texture cache that keeps track of any textures/samplers used. // Texture cache that keeps track of any textures/samplers used.
if (!texture_cache_.Initialize(&scratch_buffer_)) { if (!texture_cache_.Initialize(membase_, &scratch_buffer_)) {
PLOGE("Unable to initialize texture cache"); PLOGE("Unable to initialize texture cache");
return false; return false;
} }
@ -843,6 +843,9 @@ bool CommandProcessor::ExecutePacketType3_XE_SWAP(RingbufferReader* reader,
PrepareForWait(); PrepareForWait();
swap_handler_(swap_params); swap_handler_(swap_params);
ReturnFromWait(); ReturnFromWait();
// Remove any dead textures, etc.
texture_cache_.Scavenge();
} }
return true; return true;
} }
@ -1340,9 +1343,6 @@ bool CommandProcessor::IssueDraw(DrawCommand* draw_command) {
return IssueCopy(draw_command); return IssueCopy(draw_command);
} }
// TODO(benvanik): actually cache things >_>
texture_cache_.Clear();
// Allocate a state data block. // Allocate a state data block.
// Everything the shaders access lives here. // Everything the shaders access lives here.
auto allocation = scratch_buffer_.Acquire(sizeof(UniformDataBlock)); auto allocation = scratch_buffer_.Acquire(sizeof(UniformDataBlock));
@ -2199,10 +2199,7 @@ bool CommandProcessor::PopulateSampler(DrawCommand* draw_command,
return true; // invalid texture used return true; // invalid texture used
} }
uint32_t guest_base = fetch.address << 12; auto entry_view = texture_cache_.Demand(texture_info, sampler_info);
void* host_base = membase_ + guest_base;
auto entry_view = texture_cache_.Demand(host_base, texture_info.input_length,
texture_info, sampler_info);
if (!entry_view) { if (!entry_view) {
// Unable to create/fetch/etc. // Unable to create/fetch/etc.
XELOGE("Failed to demand texture"); XELOGE("Failed to demand texture");

View File

@ -22,138 +22,139 @@ using namespace xe::gpu::xenos;
extern "C" GLEWContext* glewGetContext(); extern "C" GLEWContext* glewGetContext();
extern "C" WGLEWContext* wglewGetContext(); extern "C" WGLEWContext* wglewGetContext();
TextureCache::TextureCache() { TextureCache::TextureCache() : membase_(nullptr), scratch_buffer_(nullptr) {
// invalidated_textures_sets_[0].reserve(64);
invalidated_textures_sets_[1].reserve(64);
invalidated_textures_ = &invalidated_textures_sets_[0];
} }
TextureCache::~TextureCache() { Shutdown(); } TextureCache::~TextureCache() { Shutdown(); }
bool TextureCache::Initialize(CircularBuffer* scratch_buffer) { bool TextureCache::Initialize(uint8_t* membase,
CircularBuffer* scratch_buffer) {
membase_ = membase;
scratch_buffer_ = scratch_buffer; scratch_buffer_ = scratch_buffer;
return true; return true;
} }
void TextureCache::Shutdown() { void TextureCache::Shutdown() { Clear(); }
Clear();
// void TextureCache::Scavenge() {
invalidated_textures_mutex_.lock();
std::vector<TextureEntry*>& invalidated_textures = *invalidated_textures_;
if (invalidated_textures_ == &invalidated_textures_sets_[0]) {
invalidated_textures_ = &invalidated_textures_sets_[1];
} else {
invalidated_textures_ = &invalidated_textures_sets_[0];
}
invalidated_textures_mutex_.unlock();
if (invalidated_textures.empty()) {
return;
}
for (auto& entry : invalidated_textures) {
EvictTexture(entry);
}
invalidated_textures.clear();
} }
void TextureCache::Clear() { void TextureCache::Clear() {
for (auto& entry : entries_) { // Kill all textures - some may be in the eviction list, but that's fine
for (auto& view : entry.views) { // as we will clear that below.
glMakeTextureHandleNonResidentARB(view.texture_sampler_handle); while (texture_entries_.size()) {
glDeleteSamplers(1, &view.sampler); auto entry = texture_entries_.begin()->second;
} EvictTexture(entry);
glDeleteTextures(1, &entry.base_texture); }
// Samplers must go last, as textures depend on them.
while (sampler_entries_.size()) {
auto entry = sampler_entries_.begin()->second;
EvictSampler(entry);
}
{
std::lock_guard<std::mutex> lock(invalidated_textures_mutex_);
invalidated_textures_sets_[0].clear();
invalidated_textures_sets_[1].clear();
} }
entries_.clear();
} }
TextureCache::EntryView* TextureCache::Demand(void* host_base, size_t length, //typedef void (*WriteWatchCallback)(void* context, void* data, void* address);
const TextureInfo& texture_info, //uintptr_t AddWriteWatch(void* address, size_t length,
const SamplerInfo& sampler_info) { // WriteWatchCallback callback, void* callback_context,
entries_.emplace_back(Entry()); // void* callback_data) {
auto& entry = entries_.back(); // //
entry.texture_info = texture_info; //}
GLenum target; TextureCache::TextureEntryView* TextureCache::Demand(
switch (texture_info.dimension) { const TextureInfo& texture_info, const SamplerInfo& sampler_info) {
case Dimension::k1D: uint64_t texture_hash = texture_info.hash();
target = GL_TEXTURE_1D; auto texture_entry = LookupOrInsertTexture(texture_info, texture_hash);
break; if (!texture_entry) {
case Dimension::k2D: PLOGE("Failed to setup texture");
target = GL_TEXTURE_2D; return nullptr;
break;
case Dimension::k3D:
target = GL_TEXTURE_3D;
break;
case Dimension::kCube:
target = GL_TEXTURE_CUBE_MAP;
break;
} }
// Setup the base texture. // We likely have the sampler in the texture view listing, so scan for it.
glCreateTextures(target, 1, &entry.base_texture); uint64_t sampler_hash = sampler_info.hash();
if (!SetupTexture(entry.base_texture, texture_info)) { for (auto& it : texture_entry->views) {
PLOGE("Unable to setup texture parameters"); if (it->sampler_hash == sampler_hash) {
return false; // Found.
return it.get();
}
} }
// Upload/convert. // No existing view found - build it.
bool uploaded = false; auto sampler_entry = LookupOrInsertSampler(sampler_info, sampler_hash);
switch (texture_info.dimension) { if (!sampler_entry) {
case Dimension::k2D: PLOGE("Failed to setup texture sampler");
uploaded = UploadTexture2D(entry.base_texture, host_base, length, return nullptr;
texture_info, sampler_info);
break;
case Dimension::k1D:
case Dimension::k3D:
case Dimension::kCube:
assert_unhandled_case(texture_info.dimension);
return false;
}
if (!uploaded) {
PLOGE("Failed to convert/upload texture");
return false;
} }
entry.views.emplace_back(EntryView()); auto view = std::make_unique<TextureEntryView>();
auto& entry_view = entry.views.back(); view->sampler = sampler_entry;
entry_view.sampler_info = sampler_info; view->sampler_hash = sampler_hash;
view->texture_sampler_handle = 0;
// Setup the sampler.
glCreateSamplers(1, &entry_view.sampler);
if (!SetupSampler(entry_view.sampler, texture_info, sampler_info)) {
PLOGE("Unable to setup texture sampler parameters");
return false;
}
// Get the uvec2 handle to the texture/sampler pair and make it resident. // Get the uvec2 handle to the texture/sampler pair and make it resident.
// The handle can be passed directly to the shader. // The handle can be passed directly to the shader.
entry_view.texture_sampler_handle = view->texture_sampler_handle = glGetTextureSamplerHandleARB(
glGetTextureSamplerHandleARB(entry.base_texture, entry_view.sampler); texture_entry->handle, sampler_entry->handle);
if (!entry_view.texture_sampler_handle) { if (!view->texture_sampler_handle) {
return nullptr; return nullptr;
} }
glMakeTextureHandleResidentARB(entry_view.texture_sampler_handle); glMakeTextureHandleResidentARB(view->texture_sampler_handle);
return &entry_view; // Entry takes ownership.
auto view_ptr = view.get();
texture_entry->views.push_back(std::move(view));
return view_ptr;
} }
bool TextureCache::SetupTexture(GLuint texture, TextureCache::SamplerEntry* TextureCache::LookupOrInsertSampler(
const TextureInfo& texture_info) { const SamplerInfo& sampler_info, uint64_t opt_hash) {
// TODO(benvanik): texture mip levels. const uint64_t hash = opt_hash ? opt_hash : sampler_info.hash();
glTextureParameteri(texture, GL_TEXTURE_BASE_LEVEL, 0); for (auto it = sampler_entries_.find(hash); it != sampler_entries_.end();
glTextureParameteri(texture, GL_TEXTURE_MAX_LEVEL, 1); ++it) {
if (it->second->sampler_info == sampler_info) {
// Found in cache!
return it->second;
}
}
// Pre-shader swizzle. // Not found, create.
// TODO(benvanik): can this be dynamic? Maybe per view? auto entry = std::make_unique<SamplerEntry>();
// We may have to emulate this in the shader. entry->sampler_info = sampler_info;
uint32_t swizzle_r = texture_info.swizzle & 0x7; glCreateSamplers(1, &entry->handle);
uint32_t swizzle_g = (texture_info.swizzle >> 3) & 0x7;
uint32_t swizzle_b = (texture_info.swizzle >> 6) & 0x7;
uint32_t swizzle_a = (texture_info.swizzle >> 9) & 0x7;
static const GLenum swizzle_map[] = {
GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_ZERO, GL_ONE,
};
glTextureParameteri(texture, GL_TEXTURE_SWIZZLE_R, swizzle_map[swizzle_r]);
glTextureParameteri(texture, GL_TEXTURE_SWIZZLE_G, swizzle_map[swizzle_g]);
glTextureParameteri(texture, GL_TEXTURE_SWIZZLE_B, swizzle_map[swizzle_b]);
glTextureParameteri(texture, GL_TEXTURE_SWIZZLE_A, swizzle_map[swizzle_a]);
return true;
}
bool TextureCache::SetupSampler(GLuint sampler, const TextureInfo& texture_info,
const SamplerInfo& sampler_info) {
// TODO(benvanik): border color from texture fetch. // TODO(benvanik): border color from texture fetch.
GLfloat border_color[4] = {0.0f}; GLfloat border_color[4] = {0.0f};
glSamplerParameterfv(sampler, GL_TEXTURE_BORDER_COLOR, border_color); glSamplerParameterfv(entry->handle, GL_TEXTURE_BORDER_COLOR, border_color);
// TODO(benvanik): setup LODs for mipmapping. // TODO(benvanik): setup LODs for mipmapping.
glSamplerParameterf(sampler, GL_TEXTURE_LOD_BIAS, 0.0f); glSamplerParameterf(entry->handle, GL_TEXTURE_LOD_BIAS, 0.0f);
glSamplerParameterf(sampler, GL_TEXTURE_MIN_LOD, 0.0f); glSamplerParameterf(entry->handle, GL_TEXTURE_MIN_LOD, 0.0f);
glSamplerParameterf(sampler, GL_TEXTURE_MAX_LOD, 0.0f); glSamplerParameterf(entry->handle, GL_TEXTURE_MAX_LOD, 0.0f);
// Texture wrapping modes. // Texture wrapping modes.
// TODO(benvanik): not sure if the middle ones are correct. // TODO(benvanik): not sure if the middle ones are correct.
@ -167,11 +168,11 @@ bool TextureCache::SetupSampler(GLuint sampler, const TextureInfo& texture_info,
GL_CLAMP_TO_BORDER, // GL_CLAMP_TO_BORDER, //
GL_MIRROR_CLAMP_TO_BORDER_EXT, // GL_MIRROR_CLAMP_TO_BORDER_EXT, //
}; };
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, glSamplerParameteri(entry->handle, GL_TEXTURE_WRAP_S,
wrap_map[sampler_info.clamp_u]); wrap_map[sampler_info.clamp_u]);
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, glSamplerParameteri(entry->handle, GL_TEXTURE_WRAP_T,
wrap_map[sampler_info.clamp_v]); wrap_map[sampler_info.clamp_v]);
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, glSamplerParameteri(entry->handle, GL_TEXTURE_WRAP_R,
wrap_map[sampler_info.clamp_w]); wrap_map[sampler_info.clamp_w]);
// Texture level filtering. // Texture level filtering.
@ -192,7 +193,7 @@ bool TextureCache::SetupSampler(GLuint sampler, const TextureInfo& texture_info,
break; break;
default: default:
assert_unhandled_case(sampler_info.mip_filter); assert_unhandled_case(sampler_info.mip_filter);
return false; return nullptr;
} }
break; break;
case ucode::TEX_FILTER_LINEAR: case ucode::TEX_FILTER_LINEAR:
@ -210,12 +211,12 @@ bool TextureCache::SetupSampler(GLuint sampler, const TextureInfo& texture_info,
break; break;
default: default:
assert_unhandled_case(sampler_info.mip_filter); assert_unhandled_case(sampler_info.mip_filter);
return false; return nullptr;
} }
break; break;
default: default:
assert_unhandled_case(sampler_info.min_filter); assert_unhandled_case(sampler_info.min_filter);
return false; return nullptr;
} }
GLenum mag_filter; GLenum mag_filter;
switch (sampler_info.mag_filter) { switch (sampler_info.mag_filter) {
@ -227,15 +228,140 @@ bool TextureCache::SetupSampler(GLuint sampler, const TextureInfo& texture_info,
break; break;
default: default:
assert_unhandled_case(mag_filter); assert_unhandled_case(mag_filter);
return false; return nullptr;
} }
glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, min_filter); glSamplerParameteri(entry->handle, GL_TEXTURE_MIN_FILTER, min_filter);
glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, mag_filter); glSamplerParameteri(entry->handle, GL_TEXTURE_MAG_FILTER, mag_filter);
// TODO(benvanik): anisotropic filtering. // TODO(benvanik): anisotropic filtering.
// GL_TEXTURE_MAX_ANISOTROPY_EXT // GL_TEXTURE_MAX_ANISOTROPY_EXT
return true; // Add to map - map takes ownership.
auto entry_ptr = entry.get();
sampler_entries_.insert({hash, entry.release()});
return entry_ptr;
}
void TextureCache::EvictSampler(SamplerEntry* entry) {
glDeleteSamplers(1, &entry->handle);
for (auto it = sampler_entries_.find(entry->sampler_info.hash());
it != sampler_entries_.end(); ++it) {
if (it->second == entry) {
sampler_entries_.erase(it);
break;
}
}
delete entry;
}
TextureCache::TextureEntry* TextureCache::LookupOrInsertTexture(
const TextureInfo& texture_info, uint64_t opt_hash) {
const uint64_t hash = opt_hash ? opt_hash : texture_info.hash();
for (auto it = texture_entries_.find(hash); it != texture_entries_.end();
++it) {
if (it->second->texture_info == texture_info) {
// Found in cache!
return it->second;
}
}
// Not found, create.
auto entry = std::make_unique<TextureEntry>();
entry->texture_info = texture_info;
entry->handle = 0;
GLenum target;
switch (texture_info.dimension) {
case Dimension::k1D:
target = GL_TEXTURE_1D;
break;
case Dimension::k2D:
target = GL_TEXTURE_2D;
break;
case Dimension::k3D:
target = GL_TEXTURE_3D;
break;
case Dimension::kCube:
target = GL_TEXTURE_CUBE_MAP;
break;
}
// Setup the base texture.
glCreateTextures(target, 1, &entry->handle);
// TODO(benvanik): texture mip levels.
glTextureParameteri(entry->handle, GL_TEXTURE_BASE_LEVEL, 0);
glTextureParameteri(entry->handle, GL_TEXTURE_MAX_LEVEL, 1);
// Pre-shader swizzle.
// TODO(benvanik): can this be dynamic? Maybe per view?
// We may have to emulate this in the shader.
uint32_t swizzle_r = texture_info.swizzle & 0x7;
uint32_t swizzle_g = (texture_info.swizzle >> 3) & 0x7;
uint32_t swizzle_b = (texture_info.swizzle >> 6) & 0x7;
uint32_t swizzle_a = (texture_info.swizzle >> 9) & 0x7;
static const GLenum swizzle_map[] = {
GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_ZERO, GL_ONE,
};
glTextureParameteri(entry->handle, GL_TEXTURE_SWIZZLE_R,
swizzle_map[swizzle_r]);
glTextureParameteri(entry->handle, GL_TEXTURE_SWIZZLE_G,
swizzle_map[swizzle_g]);
glTextureParameteri(entry->handle, GL_TEXTURE_SWIZZLE_B,
swizzle_map[swizzle_b]);
glTextureParameteri(entry->handle, GL_TEXTURE_SWIZZLE_A,
swizzle_map[swizzle_a]);
// Upload/convert.
bool uploaded = false;
switch (texture_info.dimension) {
case Dimension::k2D:
uploaded = UploadTexture2D(entry->handle, texture_info);
break;
case Dimension::k1D:
case Dimension::k3D:
case Dimension::kCube:
assert_unhandled_case(texture_info.dimension);
return false;
}
if (!uploaded) {
PLOGE("Failed to convert/upload texture");
return nullptr;
}
// AddWriteWatch(host_base, length, [](void* context_ptr, void* data_ptr,
// void* address) {
// //
//}, this, &entry);
// Add to map - map takes ownership.
auto entry_ptr = entry.get();
texture_entries_.insert({hash, entry.release()});
return entry_ptr;
}
void TextureCache::EvictTexture(TextureEntry* entry) {
/*if (entry->write_watch_handle) {
// remove from watch list
}*/
for (auto& view : entry->views) {
glMakeTextureHandleNonResidentARB(view->texture_sampler_handle);
}
glDeleteTextures(1, &entry->handle);
uint64_t texture_hash = entry->texture_info.hash();
for (auto it = texture_entries_.find(texture_hash);
it != texture_entries_.end(); ++it) {
if (it->second == entry) {
texture_entries_.erase(it);
break;
}
}
delete entry;
} }
void TextureSwap(Endian endianness, void* dest, const void* src, void TextureSwap(Endian endianness, void* dest, const void* src,
@ -266,11 +392,10 @@ void TextureSwap(Endian endianness, void* dest, const void* src,
} }
} }
bool TextureCache::UploadTexture2D(GLuint texture, void* host_base, bool TextureCache::UploadTexture2D(GLuint texture,
size_t length, const TextureInfo& texture_info) {
const TextureInfo& texture_info, auto host_address =
const SamplerInfo& sampler_info) { reinterpret_cast<const uint8_t*>(membase_ + texture_info.guest_address);
assert_true(length == texture_info.input_length);
GLenum internal_format = GL_RGBA8; GLenum internal_format = GL_RGBA8;
GLenum format = GL_RGBA; GLenum format = GL_RGBA;
@ -433,13 +558,13 @@ bool TextureCache::UploadTexture2D(GLuint texture, void* host_base,
if (texture_info.size_2d.input_pitch == if (texture_info.size_2d.input_pitch ==
texture_info.size_2d.logical_pitch) { texture_info.size_2d.logical_pitch) {
// Fast path copy entire image. // Fast path copy entire image.
TextureSwap(texture_info.endianness, allocation.host_ptr, host_base, TextureSwap(texture_info.endianness, allocation.host_ptr, host_address,
unpack_length); unpack_length);
} else { } else {
// Slow path copy row-by-row because strides differ. // Slow path copy row-by-row because strides differ.
// UNPACK_ROW_LENGTH only works for uncompressed images, and likely does // UNPACK_ROW_LENGTH only works for uncompressed images, and likely does
// this exact thing under the covers, so we just always do it here. // this exact thing under the covers, so we just always do it here.
const uint8_t* src = reinterpret_cast<const uint8_t*>(host_base); const uint8_t* src = host_address;
uint8_t* dest = reinterpret_cast<uint8_t*>(allocation.host_ptr); uint8_t* dest = reinterpret_cast<uint8_t*>(allocation.host_ptr);
for (uint32_t y = 0; y < texture_info.size_2d.block_height; y++) { for (uint32_t y = 0; y < texture_info.size_2d.block_height; y++) {
TextureSwap(texture_info.endianness, dest, src, TextureSwap(texture_info.endianness, dest, src,
@ -452,7 +577,7 @@ bool TextureCache::UploadTexture2D(GLuint texture, void* host_base,
// Untile image. // Untile image.
// We could do this in a shader to speed things up, as this is pretty slow. // We could do this in a shader to speed things up, as this is pretty slow.
// TODO(benvanik): optimize this inner loop (or work by tiles). // TODO(benvanik): optimize this inner loop (or work by tiles).
uint8_t* src = reinterpret_cast<uint8_t*>(host_base); const uint8_t* src = host_address;
uint8_t* dest = reinterpret_cast<uint8_t*>(allocation.host_ptr); uint8_t* dest = reinterpret_cast<uint8_t*>(allocation.host_ptr);
uint32_t output_pitch = uint32_t output_pitch =
(texture_info.size_2d.output_width / texture_info.block_size) * (texture_info.size_2d.output_width / texture_info.block_size) *

View File

@ -10,6 +10,8 @@
#ifndef XENIA_GPU_GL4_TEXTURE_CACHE_H_ #ifndef XENIA_GPU_GL4_TEXTURE_CACHE_H_
#define XENIA_GPU_GL4_TEXTURE_CACHE_H_ #define XENIA_GPU_GL4_TEXTURE_CACHE_H_
#include <mutex>
#include <unordered_map>
#include <vector> #include <vector>
#include <xenia/gpu/gl4/circular_buffer.h> #include <xenia/gpu/gl4/circular_buffer.h>
@ -23,39 +25,51 @@ namespace gl4 {
class TextureCache { class TextureCache {
public: public:
struct EntryView { struct SamplerEntry {
SamplerInfo sampler_info; SamplerInfo sampler_info;
GLuint sampler; GLuint handle;
};
struct TextureEntryView {
SamplerEntry* sampler;
uint64_t sampler_hash;
GLuint64 texture_sampler_handle; GLuint64 texture_sampler_handle;
}; };
struct Entry { struct TextureEntry {
TextureInfo texture_info; TextureInfo texture_info;
GLuint base_texture; GLuint handle;
std::vector<EntryView> views; std::vector<std::unique_ptr<TextureEntryView>> views;
}; };
TextureCache(); TextureCache();
~TextureCache(); ~TextureCache();
bool Initialize(CircularBuffer* scratch_buffer); bool Initialize(uint8_t* membase, CircularBuffer* scratch_buffer);
void Shutdown(); void Shutdown();
void Scavenge();
void Clear(); void Clear();
EntryView* Demand(void* host_base, size_t length, TextureEntryView* Demand(const TextureInfo& texture_info,
const TextureInfo& texture_info, const SamplerInfo& sampler_info);
const SamplerInfo& sampler_info);
private: private:
bool SetupTexture(GLuint texture, const TextureInfo& texture_info); SamplerEntry* LookupOrInsertSampler(const SamplerInfo& sampler_info,
bool SetupSampler(GLuint sampler, const TextureInfo& texture_info, uint64_t opt_hash = 0);
const SamplerInfo& sampler_info); void EvictSampler(SamplerEntry* entry);
TextureEntry* LookupOrInsertTexture(const TextureInfo& texture_info,
uint64_t opt_hash = 0);
void EvictTexture(TextureEntry* entry);
bool UploadTexture2D(GLuint texture, void* host_base, size_t length, bool UploadTexture2D(GLuint texture, const TextureInfo& texture_info);
const TextureInfo& texture_info,
const SamplerInfo& sampler_info);
uint8_t* membase_;
CircularBuffer* scratch_buffer_; CircularBuffer* scratch_buffer_;
std::vector<Entry> entries_; std::unordered_map<uint64_t, SamplerEntry*> sampler_entries_;
std::unordered_map<uint64_t, TextureEntry*> texture_entries_;
std::mutex invalidated_textures_mutex_;
std::vector<TextureEntry*>* invalidated_textures_;
std::vector<TextureEntry*> invalidated_textures_sets_[2];
}; };
} // namespace gl4 } // namespace gl4

View File

@ -9,12 +9,16 @@
#include <xenia/gpu/sampler_info.h> #include <xenia/gpu/sampler_info.h>
#include <third_party/xxhash/xxhash.h>
namespace xe { namespace xe {
namespace gpu { namespace gpu {
bool SamplerInfo::Prepare(const xenos::xe_gpu_texture_fetch_t& fetch, bool SamplerInfo::Prepare(const xenos::xe_gpu_texture_fetch_t& fetch,
const ucode::instr_fetch_tex_t& fetch_instr, const ucode::instr_fetch_tex_t& fetch_instr,
SamplerInfo* out_info) { SamplerInfo* out_info) {
std::memset(out_info, 0, sizeof(SamplerInfo));
out_info->min_filter = static_cast<ucode::instr_tex_filter_t>( out_info->min_filter = static_cast<ucode::instr_tex_filter_t>(
fetch_instr.min_filter == 3 ? fetch.min_filter : fetch_instr.min_filter); fetch_instr.min_filter == 3 ? fetch.min_filter : fetch_instr.min_filter);
out_info->mag_filter = static_cast<ucode::instr_tex_filter_t>( out_info->mag_filter = static_cast<ucode::instr_tex_filter_t>(
@ -27,5 +31,9 @@ bool SamplerInfo::Prepare(const xenos::xe_gpu_texture_fetch_t& fetch,
return true; return true;
} }
uint64_t SamplerInfo::hash() const {
return XXH64(this, sizeof(SamplerInfo), 0);
}
} // namespace gpu } // namespace gpu
} // namespace xe } // namespace xe

View File

@ -28,6 +28,7 @@ struct SamplerInfo {
const ucode::instr_fetch_tex_t& fetch_instr, const ucode::instr_fetch_tex_t& fetch_instr,
SamplerInfo* out_info); SamplerInfo* out_info);
uint64_t hash() const;
bool operator==(const SamplerInfo& other) const { bool operator==(const SamplerInfo& other) const {
return min_filter == other.min_filter && mag_filter == other.mag_filter && return min_filter == other.min_filter && mag_filter == other.mag_filter &&
mip_filter == other.mip_filter && clamp_u == other.clamp_u && mip_filter == other.mip_filter && clamp_u == other.clamp_u &&

View File

@ -9,6 +9,8 @@
#include <xenia/gpu/texture_info.h> #include <xenia/gpu/texture_info.h>
#include <third_party/xxhash/xxhash.h>
#include <poly/math.h> #include <poly/math.h>
namespace xe { namespace xe {
@ -19,13 +21,16 @@ using namespace xe::gpu::xenos;
bool TextureInfo::Prepare(const xe_gpu_texture_fetch_t& fetch, bool TextureInfo::Prepare(const xe_gpu_texture_fetch_t& fetch,
TextureInfo* out_info) { TextureInfo* out_info) {
std::memset(out_info, 0, sizeof(TextureInfo));
// http://msdn.microsoft.com/en-us/library/windows/desktop/cc308051(v=vs.85).aspx // http://msdn.microsoft.com/en-us/library/windows/desktop/cc308051(v=vs.85).aspx
// a2xx_sq_surfaceformat // a2xx_sq_surfaceformat
auto& info = *out_info; auto& info = *out_info;
info.guest_address = fetch.address << 12;
info.swizzle = fetch.swizzle; info.swizzle = fetch.swizzle;
info.dimension = static_cast<Dimension>(fetch.dimension); info.dimension = static_cast<Dimension>(fetch.dimension);
info.width = info.height = info.depth = 0;
switch (info.dimension) { switch (info.dimension) {
case Dimension::k1D: case Dimension::k1D:
info.width = fetch.size_1d.width; info.width = fetch.size_1d.width;
@ -240,5 +245,9 @@ uint32_t TextureInfo::TiledOffset2DInner(uint32_t x, uint32_t y, uint32_t bpp,
((y & 16) << 7) + (((((y & 8) >> 2) + (x >> 3)) & 3) << 6); ((y & 16) << 7) + (((((y & 8) >> 2) + (x >> 3)) & 3) << 6);
} }
uint64_t TextureInfo::hash() const {
return XXH64(this, sizeof(TextureInfo), 0);
}
} // namespace gpu } // namespace gpu
} // namespace xe } // namespace xe

View File

@ -85,6 +85,8 @@ enum class TextureFormat : uint32_t {
}; };
struct TextureInfo { struct TextureInfo {
uint32_t guest_address;
uint32_t input_length;
uint32_t swizzle; uint32_t swizzle;
Dimension dimension; Dimension dimension;
uint32_t width; uint32_t width;
@ -95,7 +97,6 @@ struct TextureInfo {
xenos::Endian endianness; xenos::Endian endianness;
bool is_tiled; bool is_tiled;
bool is_compressed; bool is_compressed;
uint32_t input_length;
TextureFormat format; TextureFormat format;
@ -129,6 +130,11 @@ struct TextureInfo {
static uint32_t TiledOffset2DInner(uint32_t x, uint32_t y, uint32_t bpp, static uint32_t TiledOffset2DInner(uint32_t x, uint32_t y, uint32_t bpp,
uint32_t base_offset); uint32_t base_offset);
uint64_t hash() const;
bool operator==(const TextureInfo& other) const {
return std::memcmp(this, &other, sizeof(TextureInfo)) == 0;
}
private: private:
void CalculateTextureSizes1D(const xenos::xe_gpu_texture_fetch_t& fetch); void CalculateTextureSizes1D(const xenos::xe_gpu_texture_fetch_t& fetch);
void CalculateTextureSizes2D(const xenos::xe_gpu_texture_fetch_t& fetch); void CalculateTextureSizes2D(const xenos::xe_gpu_texture_fetch_t& fetch);