Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ set(SRCS
include/common/ComPtr.h
include/common/afd_format.h
include/common/tlv.h
include/common/scope_guard.h
include/common/config/BuildConfig.h
include/common/config/CfgVar.h
include/common/config/GameConfig.h
Expand Down
15 changes: 15 additions & 0 deletions common/include/common/scope_guard.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#include <type_traits>

template <class F>
struct ScopeGuard {
F _f;
constexpr explicit ScopeGuard(F&& f)
noexcept(std::is_nothrow_move_constructible<F>::value)
: _f(std::forward<F>(f)) {
}
~ScopeGuard() noexcept { _f(); }
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
};
3 changes: 3 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ Version 1.5.0 (TBD): Not yet released
- Restore cut first person weapon aim sway, toggleable with `cl_weaponsway`
- Add terms of use and notices document to installer

[@is-this-c](https://github.com/is-this-c)
- Allow TrueType fonts to be rendered into bitmaps with mipmaps (D3D11 only)

### Bug fixes
[@GooberRF](https://github.com/GooberRF)
- Fix phantom visual flag mesh being visible after Salvage flag is picked up on rare occasions
Expand Down
136 changes: 136 additions & 0 deletions game_patch/bmpman/bmpman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
#include <xlog/xlog.h>
#include <common/utils/string-utils.h>
#include <common/bitmap/formats.h>
#include <common/scope_guard.h>
#include "../graphics/gr.h"
#include "../rf/file/file.h"
#include "../misc/vpackfile.h"
#include "atx.h"
#include "dds.h"
#include "stb_image_loader.h"
#include "bmpman.h"

int bm_calculate_pitch(int w, rf::bm::Format format)
{
Expand Down Expand Up @@ -244,6 +246,7 @@ FunHook<void(int)> bm_free_entry_hook{
atx_free(bm_entry);
}
bm_entry.dynamic = false;
bm_entry.user_mipmap = false;

if (!bm_entry.prev || !bm_entry.next) {
return;
Expand Down Expand Up @@ -325,6 +328,19 @@ bool bm_is_dynamic(int bm_handle)
return rf::bm::bitmaps[bm_index].dynamic;
}

void bm_set_user_mipmap(const int bm_handle, const bool mipmap) {
const int bm_index = rf::bm::get_cache_slot(bm_handle);
if (rf::bm::bitmaps[bm_index].user_mipmap != mipmap) {
rf::bm::bitmaps[bm_index].user_mipmap = mipmap;
rf::gr::mark_texture_dirty(bm_handle);
}
}

bool bm_is_user_mipmap(const int bm_handle) {
const int bm_index = rf::bm::get_cache_slot(bm_handle);
return rf::bm::bitmaps[bm_index].user_mipmap;
}

void bm_change_format(int bm_handle, rf::bm::Format format)
{
int bm_idx = rf::bm::get_cache_slot(bm_handle);
Expand All @@ -336,6 +352,126 @@ void bm_change_format(int bm_handle, rf::bm::Format format)
}
}

bool bm_copy_pixels(
const rf::gr::LockInfo& dst_lock,
int dst_x,
int dst_y,
const rf::gr::LockInfo& src_lock,
int src_x,
int src_y,
int w,
int h
) {
if (!dst_lock.data
|| !src_lock.data
|| dst_lock.bm_handle == src_lock.bm_handle
|| w <= 0
|| h <= 0)
{
return false;
}

// Clip against left and top edges cleanly.
if (src_x < 0) {
const int shift = -src_x;
w -= shift;
dst_x += shift;
src_x = 0;
}
if (dst_x < 0) {
const int shift = -dst_x;
w -= shift;
src_x += shift;
dst_x = 0;
}

// Clip against right and bottom edges.
if (src_x + w > src_lock.w) {
w = src_lock.w - src_x;
}
if (src_y + h > src_lock.h) {
h = src_lock.h - src_y;
}
if (dst_x + w > dst_lock.w) {
w = dst_lock.w - dst_x;
}
if (dst_y + h > dst_lock.h) {
h = dst_lock.h - dst_y;
}

if (w <= 0 || h <= 0) {
return false;
}

// Compute pointer offsets using bm_bytes_per_pixel AFTER clipping.
const uint8_t* src_ptr = src_lock.data
+ (src_x * bm_bytes_per_pixel(src_lock.format))
+ (src_y * src_lock.stride_in_bytes);

uint8_t* dst_ptr = dst_lock.data
+ (dst_x * bm_bytes_per_pixel(dst_lock.format))
+ (dst_y * dst_lock.stride_in_bytes);

// Row-by-row transfer.
for (int row = 0; row < h; ++row) {
bm_convert_format(
dst_ptr,
dst_lock.format,
src_ptr,
src_lock.format,
w,
1,
dst_lock.stride_in_bytes,
src_lock.stride_in_bytes,
nullptr
);
src_ptr += src_lock.stride_in_bytes;
dst_ptr += dst_lock.stride_in_bytes;
}

return true;
}

bool bm_copy(
const int dst_handle,
const int dst_x,
const int dst_y,
const int src_handle,
const int src_x,
const int src_y,
const int w,
const int h
) {
if (src_handle == dst_handle) {
return false;
}

rf::gr::LockInfo src_lock{};
if (!rf::gr::lock(src_handle, 0, &src_lock, rf::gr::LOCK_READ_ONLY)) {
return false;
}

ScopeGuard src_guard{[&] { rf::gr::unlock(&src_lock); }};

rf::gr::LockInfo dst_lock{};
if (!rf::gr::lock(dst_handle, 0, &dst_lock, rf::gr::LOCK_READ_ONLY_WRITE)) {
return false;
}

ScopeGuard dst_guard{[&] { rf::gr::unlock(&dst_lock); }};

return bm_copy_pixels(
dst_lock,
dst_x,
dst_y,
src_lock,
src_x,
src_y,
w,
h
);
}

void bm_apply_patch()
{
bm_read_header_hook.install();
Expand Down
30 changes: 28 additions & 2 deletions game_patch/bmpman/bmpman.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

#include <xlog/xlog.h>
#include "../rf/bmpman.h"
#include "../rf/gr/gr.h"

namespace rf::gr {
struct Color;
struct LockInfo;
}

// rf::bm::load never fails: a missing file gets a generated 32x32 checkerboard
// placeholder with a real handle, so "handle != -1" is not a presence test. Probe
Expand All @@ -12,16 +16,38 @@ int bm_load_if_exists(const char* name, int unk, bool generate_mipmaps);

void bm_set_dynamic(int bm_handle, bool dynamic);
bool bm_is_dynamic(int bm_handle);
void bm_set_user_mipmap(int bm_handle, bool mipmap);
bool bm_is_user_mipmap(int bm_handle);
void bm_change_format(int bm_handle, rf::bm::Format format);
void bm_apply_patch();
bool bm_is_compressed_format(rf::bm::Format format);
bool bm_convert_format(void* dst_bits_ptr, rf::bm::Format dst_fmt, const void* src_bits_ptr,
rf::bm::Format src_fmt, int width, int height, int dst_pitch, int src_pitch,
const uint8_t* palette = nullptr);
rf::Color bm_get_pixel(uint8_t* data, rf::bm::Format format, int stride_in_bytes, int x, int y);
rf::gr::Color bm_get_pixel(uint8_t* data, rf::bm::Format format, int stride_in_bytes, int x, int y);
size_t bm_calculate_total_bytes(int w, int h, rf::bm::Format format);
int bm_calculate_pitch(int w, rf::bm::Format format);
int bm_calculate_rows(int h, rf::bm::Format format);
bool bm_copy_pixels(
const rf::gr::LockInfo& dst_lock,
int dst_x,
int dst_y,
const rf::gr::LockInfo& src_lock,
int src_x,
int src_y,
int w,
int h
);
bool bm_copy(
int dst_handle,
int dst_x,
int dst_y,
int src_handle,
int src_x,
int src_y,
int w,
int h
);

inline int bm_bytes_per_pixel(rf::bm::Format format)
{
Expand Down
2 changes: 1 addition & 1 deletion game_patch/bmpman/fmt_conv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ bool bm_convert_format(void* dst_bits_ptr, rf::bm::Format dst_fmt, const void* s
}
}

rf::Color bm_get_pixel(uint8_t* data, rf::bm::Format format, int stride_in_bytes, int x, int y)
rf::gr::Color bm_get_pixel(uint8_t* data, rf::bm::Format format, int stride_in_bytes, int x, int y)
{
if (bm_is_compressed_format(format)) {
constexpr int block_w = 4;
Expand Down
56 changes: 44 additions & 12 deletions game_patch/graphics/d3d11/gr_d3d11_texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ namespace gr::d3d11
constexpr UINT required_support =
D3D11_FORMAT_SUPPORT_RENDER_TARGET | D3D11_FORMAT_SUPPORT_MIP_AUTOGEN;
if ((format_support & required_support) != required_support) {
if (rf::bm::get_type(bm_handle) == rf::bm::TYPE_USER) {
xlog::warn(
"Auto-mip is not supported for user bitmaps that are format {}",
static_cast<int>(fmt)
);
return create_texture(bm_handle, fmt, w, h, bits, pal, 1, false, w, h);
}
bool has_alpha = dxgi_format == DXGI_FORMAT_B5G5R5A1_UNORM
|| dxgi_format == DXGI_FORMAT_B4G4R4A4_UNORM
|| dxgi_format == DXGI_FORMAT_B8G8R8A8_UNORM;
Expand All @@ -180,26 +187,33 @@ namespace gr::d3d11
return create_texture(bm_handle, fmt, w, h, bits, pal, 1, false, w, h);
}

std::unique_ptr<rf::ubyte[]> converted_bits;
rf::ubyte* upload_bits = bits;
int upload_pitch = bm_calculate_pitch(w, fmt);
if (fmt != supported_fmt) {
int dst_pitch = bm_calculate_pitch(w, supported_fmt);
converted_bits = std::make_unique<rf::ubyte[]>(dst_pitch * h);
::bm_convert_format(converted_bits.get(), supported_fmt, bits, fmt, w, h, dst_pitch, upload_pitch, pal);
upload_bits = converted_bits.get();
upload_pitch = dst_pitch;
}
// Only convert and upload, if we have pixel data.
if (bits) {
std::unique_ptr<rf::ubyte[]> converted_bits;
rf::ubyte* upload_bits = bits;
int upload_pitch = bm_calculate_pitch(w, fmt);
if (fmt != supported_fmt) {
int dst_pitch = bm_calculate_pitch(w, supported_fmt);
converted_bits = std::make_unique<rf::ubyte[]>(dst_pitch * h);
::bm_convert_format(converted_bits.get(), supported_fmt, bits, fmt, w, h, dst_pitch, upload_pitch, pal);
upload_bits = converted_bits.get();
upload_pitch = dst_pitch;
}

device_context_->UpdateSubresource(d3d_texture, 0, nullptr, upload_bits, upload_pitch, 0);
device_context_->UpdateSubresource(d3d_texture, 0, nullptr, upload_bits, upload_pitch, 0);
}

ComPtr<ID3D11ShaderResourceView> srv;
hr = device_->CreateShaderResourceView(d3d_texture, nullptr, &srv);
if (FAILED(hr)) {
xlog::warn("Auto-mip SRV creation failed ({}x{}), falling back to single mip", w, h);
return create_texture(bm_handle, fmt, w, h, bits, pal, 1, false, w, h);
}
device_context_->GenerateMips(srv);

// Only generate mips initially, if we uploaded data.
if (bits) {
device_context_->GenerateMips(srv);
}

Texture texture{bm_handle, dxgi_format, {}};
texture.gpu_texture = std::move(d3d_texture);
Expand Down Expand Up @@ -286,6 +300,9 @@ namespace gr::d3d11

if (rf::bm::get_type(bm_handle) == rf::bm::TYPE_USER) {
xlog::trace("Creating user bitmap texture: handle {}", bm_handle);
if (bm_is_user_mipmap(bm_handle) && !staging) {
return create_texture_auto_mips(bm_handle, fmt, w, h, nullptr, nullptr);
}
auto texture = create_texture(bm_handle, fmt, w, h, nullptr, nullptr, 1, staging);
return texture;
}
Expand Down Expand Up @@ -572,12 +589,27 @@ namespace gr::d3d11

void TextureManager::unlock(rf::gr::LockInfo *lock)
{
if (!lock) {
return;
}
xlog::trace("unlocking texture: handle {} format {} size {}x{} data {}", lock->bm_handle, lock->format, lock->w, lock->h, lock->data);
Texture& texture = get_or_load_texture(lock->bm_handle, true);
if (texture.cpu_texture) {
device_context_->Unmap(texture.cpu_texture, 0);
if (lock->mode != rf::gr::LOCK_READ_ONLY && texture.gpu_texture) {
device_context_->CopySubresourceRegion(texture.gpu_texture, 0, 0, 0, 0, texture.cpu_texture, 0, nullptr);
// We need to rebuild each mip level.
if (bm_is_user_mipmap(lock->bm_handle)) {
D3D11_TEXTURE2D_DESC desc{};
texture.gpu_texture->GetDesc(&desc);
if (desc.MiscFlags & D3D11_RESOURCE_MISC_GENERATE_MIPS) {
ID3D11ShaderResourceView* srv =
texture.get_or_create_texture_view(device_, device_context_);
if (srv) {
device_context_->GenerateMips(srv);
}
}
}
}
}
}
Expand Down
Loading
Loading