renderer: Add a missing texture asset and a user check

When an asset is missing, instead of a black screen, render an obnoxious, yet standard, missing texture.

Additionally, warn the user assets failed to load.

Shoutout to Arch for having their assets broken for months. Fix your shit. I am tired of it, and it's negatively impacting users.
This commit is contained in:
Vaxry 2024-11-01 15:52:03 +00:00
parent 3852418d24
commit d8b865366a
5 changed files with 139 additions and 71 deletions

View file

@ -2738,6 +2738,12 @@ void CCompositor::performUserChecks() {
CColor{}, 15000, ICON_WARNING); CColor{}, 15000, ICON_WARNING);
} }
} }
if (g_pHyprOpenGL->failedAssetsNo > 0) {
g_pHyprNotificationOverlay->addNotification(std::format("Hyprland failed to load {} essential asset{}, blame your distro's packager for doing a bad job at packaging!",
g_pHyprOpenGL->failedAssetsNo, g_pHyprOpenGL->failedAssetsNo > 1 ? "s" : ""),
CColor{1.0, 0.1, 0.1, 1.0}, 15000, ICON_ERROR);
}
} }
void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWorkspace) { void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWorkspace) {

View file

@ -2,7 +2,7 @@
#include "OpenGL.hpp" #include "OpenGL.hpp"
CFramebuffer::CFramebuffer() { CFramebuffer::CFramebuffer() {
m_cTex = makeShared<CTexture>(); ;
} }
bool CFramebuffer::alloc(int w, int h, uint32_t drmFormat) { bool CFramebuffer::alloc(int w, int h, uint32_t drmFormat) {
@ -12,6 +12,9 @@ bool CFramebuffer::alloc(int w, int h, uint32_t drmFormat) {
uint32_t glFormat = FormatUtils::drmFormatToGL(drmFormat); uint32_t glFormat = FormatUtils::drmFormatToGL(drmFormat);
uint32_t glType = FormatUtils::glFormatToType(glFormat); uint32_t glType = FormatUtils::glFormatToType(glFormat);
if (!m_cTex)
m_cTex = makeShared<CTexture>();
if (!m_iFbAllocated) { if (!m_iFbAllocated) {
firstAlloc = true; firstAlloc = true;
glGenFramebuffers(1, &m_iFb); glGenFramebuffers(1, &m_iFb);
@ -54,7 +57,8 @@ bool CFramebuffer::alloc(int w, int h, uint32_t drmFormat) {
} }
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, g_pHyprOpenGL->m_iCurrentOutputFb); if (g_pHyprOpenGL)
glBindFramebuffer(GL_FRAMEBUFFER, g_pHyprOpenGL->m_iCurrentOutputFb);
m_vSize = Vector2D(w, h); m_vSize = Vector2D(w, h);
@ -85,14 +89,17 @@ void CFramebuffer::bind() {
#else #else
glBindFramebuffer(GL_FRAMEBUFFER, m_iFb); glBindFramebuffer(GL_FRAMEBUFFER, m_iFb);
#endif #endif
glViewport(0, 0, g_pHyprOpenGL->m_RenderData.pMonitor->vecPixelSize.x, g_pHyprOpenGL->m_RenderData.pMonitor->vecPixelSize.y); if (g_pHyprOpenGL)
glViewport(0, 0, g_pHyprOpenGL->m_RenderData.pMonitor->vecPixelSize.x, g_pHyprOpenGL->m_RenderData.pMonitor->vecPixelSize.y);
else
glViewport(0, 0, m_vSize.x, m_vSize.y);
} }
void CFramebuffer::release() { void CFramebuffer::release() {
if (m_iFbAllocated) if (m_iFbAllocated)
glDeleteFramebuffers(1, &m_iFb); glDeleteFramebuffers(1, &m_iFb);
m_cTex->destroyTexture(); m_cTex.reset();
m_iFbAllocated = false; m_iFbAllocated = false;
m_vSize = Vector2D(); m_vSize = Vector2D();
} }

View file

@ -13,6 +13,13 @@
#include <gbm.h> #include <gbm.h>
#include <filesystem> #include <filesystem>
const std::vector<const char*> ASSET_PATHS = {
#ifdef DATAROOTDIR
DATAROOTDIR,
#endif
"/usr/share",
};
inline void loadGLProc(void* pProc, const char* name) { inline void loadGLProc(void* pProc, const char* name) {
void* proc = (void*)eglGetProcAddress(name); void* proc = (void*)eglGetProcAddress(name);
if (proc == NULL) { if (proc == NULL) {
@ -2595,11 +2602,32 @@ void CHyprOpenGLImpl::renderSplash(cairo_t* const CAIRO, cairo_surface_t* const
cairo_surface_flush(CAIROSURFACE); cairo_surface_flush(CAIROSURFACE);
} }
SP<CTexture> CHyprOpenGLImpl::loadAsset(const std::string& file) { SP<CTexture> CHyprOpenGLImpl::loadAsset(const std::string& filename) {
const auto CAIROSURFACE = cairo_image_surface_create_from_png(file.c_str());
if (!CAIROSURFACE) std::string fullPath;
return nullptr; for (auto& e : ASSET_PATHS) {
std::string p = std::string{e} + "/hypr/" + filename;
std::error_code ec;
if (std::filesystem::exists(p, ec)) {
fullPath = p;
break;
} else
Debug::log(LOG, "loadAsset: looking at {} unsuccessful: ec {}", filename, ec.message());
}
if (fullPath.empty()) {
failedAssetsNo++;
Debug::log(ERR, "loadAsset: looking for {} failed (no provider found)", filename);
return m_pMissingAssetTexture;
}
const auto CAIROSURFACE = cairo_image_surface_create_from_png(fullPath.c_str());
if (!CAIROSURFACE) {
failedAssetsNo++;
Debug::log(ERR, "loadAsset: failed to load {} (corrupt / inaccessible / not png)", fullPath);
return m_pMissingAssetTexture;
}
const auto CAIROFORMAT = cairo_image_surface_get_format(CAIROSURFACE); const auto CAIROFORMAT = cairo_image_surface_get_format(CAIROSURFACE);
auto tex = makeShared<CTexture>(); auto tex = makeShared<CTexture>();
@ -2710,51 +2738,68 @@ SP<CTexture> CHyprOpenGLImpl::renderText(const std::string& text, CColor col, in
return tex; return tex;
} }
void CHyprOpenGLImpl::initAssets() { void CHyprOpenGLImpl::initMissingAssetTexture() {
std::string assetsPath = ""; SP<CTexture> tex = makeShared<CTexture>();
#ifndef DATAROOTDIR tex->allocate();
assetsPath = "/usr/share/hypr/";
#else
assetsPath = std::format("{}{}", DATAROOTDIR, "/hypr/");
#endif
m_pLockDeadTexture = loadAsset(assetsPath + "lockdead.png"); const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 512, 512);
m_pLockDead2Texture = loadAsset(assetsPath + "lockdead2.png"); const auto CAIRO = cairo_create(CAIROSURFACE);
cairo_set_antialias(CAIRO, CAIRO_ANTIALIAS_NONE);
cairo_save(CAIRO);
cairo_set_source_rgba(CAIRO, 0, 0, 0, 1);
cairo_set_operator(CAIRO, CAIRO_OPERATOR_SOURCE);
cairo_paint(CAIRO);
cairo_set_source_rgba(CAIRO, 1, 0, 1, 1);
cairo_rectangle(CAIRO, 256, 0, 256, 256);
cairo_fill(CAIRO);
cairo_rectangle(CAIRO, 0, 256, 256, 256);
cairo_fill(CAIRO);
cairo_restore(CAIRO);
cairo_surface_flush(CAIROSURFACE);
tex->m_vSize = {512, 512};
// copy the data to an OpenGL texture we have
const GLint glFormat = GL_RGBA;
const GLint glType = GL_UNSIGNED_BYTE;
const auto DATA = cairo_image_surface_get_data(CAIROSURFACE);
glBindTexture(GL_TEXTURE_2D, tex->m_iTexID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
#ifndef GLES2
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
#endif
glTexImage2D(GL_TEXTURE_2D, 0, glFormat, tex->m_vSize.x, tex->m_vSize.y, 0, glFormat, glType, DATA);
cairo_surface_destroy(CAIROSURFACE);
cairo_destroy(CAIRO);
m_pMissingAssetTexture = tex;
}
void CHyprOpenGLImpl::initAssets() {
initMissingAssetTexture();
static auto PFORCEWALLPAPER = CConfigValue<Hyprlang::INT>("misc:force_default_wallpaper");
const auto FORCEWALLPAPER = std::clamp(*PFORCEWALLPAPER, static_cast<int64_t>(-1L), static_cast<int64_t>(2L));
m_pLockDeadTexture = loadAsset("lockdead.png");
m_pLockDead2Texture = loadAsset("lockdead2.png");
m_pLockTtyTextTexture = renderText(std::format("Running on tty {}", m_pLockTtyTextTexture = renderText(std::format("Running on tty {}",
g_pCompositor->m_pAqBackend->hasSession() && g_pCompositor->m_pAqBackend->session->vt > 0 ? g_pCompositor->m_pAqBackend->hasSession() && g_pCompositor->m_pAqBackend->session->vt > 0 ?
std::to_string(g_pCompositor->m_pAqBackend->session->vt) : std::to_string(g_pCompositor->m_pAqBackend->session->vt) :
"unknown"), "unknown"),
CColor{0.9F, 0.9F, 0.9F, 0.7F}, 20, true); CColor{0.9F, 0.9F, 0.9F, 0.7F}, 20, true);
}
void CHyprOpenGLImpl::createBGTextureForMonitor(PHLMONITOR pMonitor) { // create the default background texture
RASSERT(m_RenderData.pMonitor, "Tried to createBGTex without begin()!"); {
std::string texPath = std::format("{}", "wall");
Debug::log(LOG, "Creating a texture for BGTex");
static auto PRENDERTEX = CConfigValue<Hyprlang::INT>("misc:disable_hyprland_logo");
static auto PNOSPLASH = CConfigValue<Hyprlang::INT>("misc:disable_splash_rendering");
static auto PFORCEWALLPAPER = CConfigValue<Hyprlang::INT>("misc:force_default_wallpaper");
const auto FORCEWALLPAPER = std::clamp(*PFORCEWALLPAPER, static_cast<int64_t>(-1L), static_cast<int64_t>(2L));
if (*PRENDERTEX)
return;
// release the last tex if exists
const auto PFB = &m_mMonitorBGFBs[pMonitor];
PFB->release();
PFB->alloc(pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y, pMonitor->output->state->state().drmFormat);
if (!m_pBackgroundTexture) {
std::string texPath = "";
#ifndef DATAROOTDIR
texPath = "/usr/share/hypr/wall";
#else
texPath = std::format("{}{}", DATAROOTDIR, "/hypr/wall");
#endif
// get the adequate tex // get the adequate tex
if (FORCEWALLPAPER == -1) { if (FORCEWALLPAPER == -1) {
@ -2767,15 +2812,29 @@ void CHyprOpenGLImpl::createBGTextureForMonitor(PHLMONITOR pMonitor) {
texPath += ".png"; texPath += ".png";
// check if wallpapers exist
std::error_code err;
if (!std::filesystem::exists(texPath, err)) {
Debug::log(ERR, "createBGTextureForMonitor: failed, file \"{}\" doesn't exist or access denied, ec: {}", texPath, err.message());
return; // the texture will be empty, oh well. We'll clear with a solid color anyways.
}
m_pBackgroundTexture = loadAsset(texPath); m_pBackgroundTexture = loadAsset(texPath);
} }
}
void CHyprOpenGLImpl::createBGTextureForMonitor(PHLMONITOR pMonitor) {
RASSERT(m_RenderData.pMonitor, "Tried to createBGTex without begin()!");
Debug::log(LOG, "Creating a texture for BGTex");
static auto PRENDERTEX = CConfigValue<Hyprlang::INT>("misc:disable_hyprland_logo");
static auto PNOSPLASH = CConfigValue<Hyprlang::INT>("misc:disable_splash_rendering");
if (*PRENDERTEX)
return;
// release the last tex if exists
const auto PFB = &m_mMonitorBGFBs[pMonitor];
PFB->release();
PFB->alloc(pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y, pMonitor->output->state->state().drmFormat);
if (!m_pBackgroundTexture) // ?!?!?!
return;
// create a new one with cairo // create a new one with cairo
SP<CTexture> tex = makeShared<CTexture>(); SP<CTexture> tex = makeShared<CTexture>();

View file

@ -215,11 +215,12 @@ class CHyprOpenGLImpl {
GLint m_iCurrentOutputFb = 0; GLint m_iCurrentOutputFb = 0;
int m_iGBMFD = -1; int m_iGBMFD = -1;
gbm_device* m_pGbmDevice = nullptr; gbm_device* m_pGbmDevice = nullptr;
EGLContext m_pEglContext = nullptr; EGLContext m_pEglContext = nullptr;
EGLDisplay m_pEglDisplay = nullptr; EGLDisplay m_pEglDisplay = nullptr;
EGLDeviceEXT m_pEglDevice = nullptr; EGLDeviceEXT m_pEglDevice = nullptr;
uint failedAssetsNo = 0;
bool m_bReloadScreenShader = true; // at launch it can be set bool m_bReloadScreenShader = true; // at launch it can be set
@ -277,7 +278,7 @@ class CHyprOpenGLImpl {
CShader m_sFinalScreenShader; CShader m_sFinalScreenShader;
CTimer m_tGlobalTimer; CTimer m_tGlobalTimer;
SP<CTexture> m_pBackgroundTexture, m_pLockDeadTexture, m_pLockDead2Texture, m_pLockTtyTextTexture; SP<CTexture> m_pMissingAssetTexture, m_pBackgroundTexture, m_pLockDeadTexture, m_pLockDead2Texture, m_pLockTtyTextTexture;
void logShaderError(const GLuint&, bool program = false); void logShaderError(const GLuint&, bool program = false);
GLuint createProgram(const std::string&, const std::string&, bool dynamic = false); GLuint createProgram(const std::string&, const std::string&, bool dynamic = false);
@ -290,6 +291,7 @@ class CHyprOpenGLImpl {
SP<CTexture> loadAsset(const std::string& file); SP<CTexture> loadAsset(const std::string& file);
SP<CTexture> renderText(const std::string& text, CColor col, int pt, bool italic = false); SP<CTexture> renderText(const std::string& text, CColor col, int pt, bool italic = false);
void initAssets(); void initAssets();
void initMissingAssetTexture();
// //
std::optional<std::vector<uint64_t>> getModsForFormat(EGLint format); std::optional<std::vector<uint64_t>> getModsForFormat(EGLint format);

View file

@ -1063,22 +1063,16 @@ void CHyprRenderer::renderSessionLockMissing(PHLMONITOR pMonitor) {
if (ANY_PRESENT) { if (ANY_PRESENT) {
// render image2, without instructions. Lock still "alive", unless texture dead // render image2, without instructions. Lock still "alive", unless texture dead
if (g_pHyprOpenGL->m_pLockDead2Texture) g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_pLockDead2Texture, &monbox, ALPHA);
g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_pLockDead2Texture, &monbox, ALPHA);
else
g_pHyprOpenGL->renderRect(&monbox, CColor(1.0, 0.2, 0.2, ALPHA));
} else { } else {
// render image, with instructions. Lock is gone. // render image, with instructions. Lock is gone.
if (g_pHyprOpenGL->m_pLockDeadTexture) { g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_pLockDeadTexture, &monbox, ALPHA);
g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_pLockDeadTexture, &monbox, ALPHA);
// also render text for the tty number // also render text for the tty number
if (g_pHyprOpenGL->m_pLockTtyTextTexture) { if (g_pHyprOpenGL->m_pLockTtyTextTexture) {
CBox texbox = {{}, g_pHyprOpenGL->m_pLockTtyTextTexture->m_vSize}; CBox texbox = {{}, g_pHyprOpenGL->m_pLockTtyTextTexture->m_vSize};
g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_pLockTtyTextTexture, &texbox, 1.F); g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_pLockTtyTextTexture, &texbox, 1.F);
} }
} else
g_pHyprOpenGL->renderRect(&monbox, CColor(1.0, 0.2, 0.2, ALPHA));
} }
if (ALPHA < 1.f) /* animate */ if (ALPHA < 1.f) /* animate */