A texture cache that never invalidates.
This commit is contained in:
parent
79179c28ef
commit
718762746a
|
@ -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);
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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) *
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 &&
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue