Name entry on screen and with a controller; AMD command buffer fixes (1.4)

- The game's text dialog (the character name) was only shown in the window title and took
  only the keyboard: in fullscreen or with a controller the character creation screen
  looked frozen. It is now a box on screen (overlay), starts with the launcher's player name
  or "Hunter", and Cross/Circle accept or cancel it. The game gets neutral input while it
  is open and until the buttons are released.
- ExtraSharpen (sharpness above 1) and an FSR 4 rebuild (preset or output change) called
  Scheduler::Finish(), which submits the command buffer the caller is recording, and then
  kept recording into it: NVIDIA tolerates that, AMD's driver crashes. They now wait for
  the submitted work only (Scheduler::WaitSubmitted). Same issue as IFreemz's fix in
  shadPS4-Bloodborne-DLSS-FSR (3a02269).
This commit is contained in:
Supermedo committed 2026-10-07 06:48:38 +02:00
1 parent edfe859f31
commit 1d34202b73
10 files changed
+97 -15

No files matched your search

@@ -79,11 +79,17 @@ struct Fsr4Upscaler::Impl {
Destroy();
}
void Destroy() {
/// `recording`: called while a frame's command buffer is being recorded; it is left open
/// (Finish() would submit it under the caller).
void Destroy(bool recording = false) {
if (!backend_ok && !context_ok) {
return;
}
scheduler.Finish();
if (recording) {
scheduler.WaitSubmitted();
} else {
scheduler.Finish();
}
if (context_ok) {
ffxFsr4V07DestroyContext(&context, nullptr);
context_ok = false;
@@ -283,9 +289,9 @@ struct Fsr4Upscaler::Impl {
const int preset = std::clamp(f.preset, 0, 4);
if (!context_ok || preset != model || f.output.width != out_width ||
f.output.height != out_height) {
Destroy();
Destroy(true);
if (!CreateBackend(preset, f.output.width, f.output.height)) {
Destroy();
Destroy(true);
return false;
}
}
@@ -332,6 +332,13 @@ void Scheduler::Finish() {
Wait(presubmit_tick);
}
void Scheduler::WaitSubmitted() {
const u64 tick = CurrentTick();
if (tick > 1) {
Wait(tick - 1);
}
}
void Scheduler::Wait(u64 tick) {
if (tick >= work_semaphore.CurrentTick()) {
// Make sure we are not waiting for the current tick without signalling
@@ -682,6 +682,11 @@ public:
/// Sends the current execution context to the GPU and waits for it to complete.
void Finish();
/// bbport: waits for the work submitted so far but leaves the command buffer being
/// recorded open, so a CommandBuffer() the caller holds stays valid (Finish() submits it:
/// recording into a submitted buffer crashes AMD's driver).
void WaitSubmitted();
/// Waits for the given tick to trigger on the GPU.
void Wait(u64 tick);
@@ -728,8 +728,8 @@ void TemporalUpscaler::ExtraSharpen(vk::CommandBuffer cmdbuf, vk::Image target,
}
const auto device = instance.GetDevice();
if (extra_sharpen_width != w || extra_sharpen_height != h) {
// Retired command buffers may still read the old copy.
scheduler.Finish();
// Retired command buffers may still read the old copy. `cmdbuf` stays open.
scheduler.WaitSubmitted();
extra_sharpen_view.reset();
extra_sharpen_image = VideoCore::UniqueImage(device, instance.GetAllocator());
extra_sharpen_image.Create(vk::ImageCreateInfo{
+40 -2
View File
@@ -2,6 +2,7 @@
#include "bbport_overlay.h"
#include <atomic>
#include <cfloat>
#include <chrono>
#include <cmath>
#include <cstdio>
@@ -51,6 +52,9 @@ bool initialized = false;
std::atomic<bool> menu_open{false};
bool l3_down = false, r3_down = false;
bool dirty = false; // settings changed while open: saved on close
// The game's text dialog (SetTextEntry), guarded by imgui_mutex.
bool text_entry_active = false;
std::string text_entry_prompt, text_entry_text;
float base_scale = 1.0f;
// Present rate for the FPS counter.
@@ -389,6 +393,30 @@ void Menu() {
}
}
// The game's text dialog: what is typed, and how to finish (keyboard or controller).
void TextEntryBox() {
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(ImVec2(viewport->WorkPos.x + viewport->WorkSize.x * 0.5f,
viewport->WorkPos.y + viewport->WorkSize.y * 0.42f),
ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGui::SetNextWindowSizeConstraints(ImVec2(viewport->WorkSize.x * 0.32f, 0.0f),
ImVec2(viewport->WorkSize.x * 0.8f, FLT_MAX));
ImGui::SetNextWindowBgAlpha(0.92f);
ImGui::Begin("##text_entry", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_NoFocusOnAppearing);
ImGui::TextColored(ImVec4(0.85f, 0.72f, 0.45f, 1.0f), "%s", text_entry_prompt.c_str());
ImGui::Separator();
ImGui::SetWindowFontScale(1.4f);
ImGui::Text("%s_", text_entry_text.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::Separator();
ImGui::TextDisabled("Keyboard: type, Backspace to erase, Enter = OK, Esc = cancel");
ImGui::TextDisabled("Controller: Cross (A) = OK, Circle (B) = cancel");
ImGui::End();
}
void FpsCounter() {
const ImGuiViewport* viewport = ImGui::GetMainViewport();
const float pad = 12.0f * base_scale;
@@ -579,11 +607,18 @@ bool HandleEvent(const SDL_Event& event) {
}
bool Visible() {
return initialized && (menu_open || BbSettings::Get().show_fps);
return initialized && (menu_open || text_entry_active || BbSettings::Get().show_fps);
}
bool CapturesInput() {
return menu_open;
return menu_open || text_entry_active;
}
void SetTextEntry(bool active, const std::string& prompt, const std::string& text) {
std::scoped_lock lock{imgui_mutex};
text_entry_active = active;
text_entry_prompt = prompt;
text_entry_text = text;
}
void Render(vk::CommandBuffer cmdbuf, vk::ImageView view, vk::Extent2D extent) {
@@ -618,6 +653,9 @@ void Render(vk::CommandBuffer cmdbuf, vk::ImageView view, vk::Extent2D extent) {
if (BbSettings::Get().show_fps && !menu_open) {
FpsCounter();
}
if (text_entry_active) {
TextEntryBox();
}
ImGui::Render();
const vk::RenderingAttachmentInfo attachment{
+6 -1
View File
@@ -5,6 +5,7 @@
#pragma once
#include <string>
#include "common/types.h"
#include "video_core/renderer_vulkan/vk_common.h"
@@ -31,7 +32,11 @@ bool Visible();
/// Present thread: draws into `view` (layout ColorAttachmentOptimal).
void Render(vk::CommandBuffer cmdbuf, vk::ImageView view, vk::Extent2D extent);
/// The menu is open: the game's input is held neutral.
/// The menu is open or a text entry is shown: the game's input is held neutral.
bool CapturesInput();
/// Window thread: the game's text dialog (sceImeDialog). Shown as a box on screen until
/// `active` is false; typing goes to the window (sdl_window), the box only displays it.
void SetTextEntry(bool active, const std::string& prompt, const std::string& text);
} // namespace BbOverlay
+12
View File
@@ -77,6 +77,7 @@ void WindowSDL::UpdateTextTitle() {
const std::string title = text_active ? base_title + " \u2014 " + text_prompt + ": " + text + "_ (Enter = OK, Esc = cancel)"
: base_title;
SDL_SetWindowTitle(window, title.c_str());
BbOverlay::SetTextEntry(text_active, text_prompt, text);
}
bool WindowSDL::PollEvents() {
@@ -110,6 +111,17 @@ bool WindowSDL::PollEvents() {
UpdateTextTitle();
continue;
}
// The controller finishes the text dialog too: Cross (A) accepts, Circle (B) cancels.
if (text_active && event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN &&
(event.gbutton.button == SDL_GAMEPAD_BUTTON_SOUTH ||
event.gbutton.button == SDL_GAMEPAD_BUTTON_EAST)) {
std::scoped_lock lock{text_mutex};
text_state = event.gbutton.button == SDL_GAMEPAD_BUTTON_SOUTH ? 1 : 2;
text_active = false;
SDL_StopTextInput(window);
UpdateTextTitle();
continue;
}
if (BbOverlay::HandleEvent(event)) {
continue;
}
+1 -1
View File
@@ -38,7 +38,7 @@ PATCH_VERSION = '01.09'
MAX_LOG_LINES = 6000
NO_WINDOW = getattr(subprocess, 'CREATE_NO_WINDOW', 0)
# This build; GitHub release tags are windows-v<VERSION>.
VERSION = '1.3'
VERSION = '1.4'
RELEASES_API = 'https://api.github.com/repos/Supermedo/bloodborne_pc/releases/latest'
RELEASES_PAGE = 'https://github.com/Supermedo/bloodborne_pc/releases/latest'
UPDATE_DIR = Path(tempfile.gettempdir()) / 'bbport-update'
+8 -1
View File
@@ -270,9 +270,12 @@ static void replay_sample(PadData *d) {
d->left_x=s->axes[0]; d->left_y=s->axes[1]; d->right_x=s->axes[2]; d->right_y=s->axes[3];
d->l2=s->l2; d->r2=s->r2;
}
/* After the menu or the text dialog closes, buttons still held (the Cross that accepted a
* name) stay hidden until released: the game would take them as a new press. */
static int hold_after_capture;
static void sample(PadData *d) {
sample_host(d);
if (bbgpu_overlay_captures_input()) return;
if (bbgpu_overlay_captures_input()) { hold_after_capture=1; return; }
record_sample(d);
read_inject();
replay_sample(d);
@@ -283,6 +286,10 @@ static void sample(PadData *d) {
if (injected.buttons & BTN_R2) d->r2=255;
uint8_t *axes[4]={&d->left_x,&d->left_y,&d->right_x,&d->right_y};
for (int i=0;i<4;++i) if (injected.stick[i]>=0) *axes[i]=(uint8_t)injected.stick[i];
if (hold_after_capture) {
if (d->buttons) d->buttons=0;
else hold_after_capture=0;
}
}
static ABI int32_t pad_init(void) { pthread_mutex_lock(&lock); initialized=1; pthread_mutex_unlock(&lock); return 0; }
+6 -4
View File
@@ -258,10 +258,12 @@ static ABI int32_t ime_init(const ImeParam *param, const void *extended) {
utf16_to_utf8(param->title,128,prompt,sizeof(prompt));
const char *preset=getenv("BB_IME_TEXT");
if (preset) { ime_complete(0,preset); return 0; }
if (!bbgpu_text_input_begin(initial,prompt[0] ? prompt : "Text")) {
const char *name=getenv("BB_USER_NAME");
ime_complete(0,name ? name : initial[0] ? initial : "Hunter");
} else printf("Runtime: ImeDialog opened: type in the game window, Enter to confirm, Esc to cancel\n");
/* An empty field starts with the player name (launcher) or "Hunter": a controller alone
* can accept it (the port has no on-screen keyboard). */
const char *name=getenv("BB_USER_NAME");
const char *start=initial[0] ? initial : name && *name ? name : "Hunter";
if (!bbgpu_text_input_begin(start,prompt[0] ? prompt : "Text")) ime_complete(0,start);
else printf("Runtime: ImeDialog opened: shown on screen; keyboard Enter or Cross accepts, Esc or Circle cancels\n");
return 0;
}
static ABI int32_t ime_status(void) {