|
5 | 5 | #include <Library/LibrarySettings.hpp> |
6 | 6 |
|
7 | 7 | #include <score/application/ApplicationContext.hpp> |
| 8 | +#include <score/tools/File.hpp> |
| 9 | +#include <score/tools/FilePath.hpp> |
| 10 | + |
| 11 | +#include <core/document/Document.hpp> |
8 | 12 |
|
9 | 13 | #include <ossia/detail/flat_map.hpp> |
10 | 14 | #include <ossia/detail/hash_map.hpp> |
@@ -597,36 +601,82 @@ ProgramCache::get(const ShaderSource& program, const QString& originPath) noexce |
597 | 601 | return {std::nullopt, "Unknown error"}; |
598 | 602 | } |
599 | 603 |
|
| 604 | +static QString shaderSibling(const QString& path, std::initializer_list<QLatin1String> exts) |
| 605 | +{ |
| 606 | + const QFileInfo fi{path}; |
| 607 | + // completeBaseName, not baseName: `my.shader.fs` pairs with `my.shader.vs`, |
| 608 | + // and path surgery on the string as a whole would also rewrite a folder |
| 609 | + // called `fs-shaders`. |
| 610 | + const QString base = fi.path() + '/' + fi.completeBaseName() + '.'; |
| 611 | + for(const QLatin1String& ext : exts) |
| 612 | + { |
| 613 | + if(fi.suffix() == ext) |
| 614 | + continue; |
| 615 | + if(const QString candidate = base + ext; QFileInfo::exists(candidate)) |
| 616 | + return candidate; |
| 617 | + } |
| 618 | + return {}; |
| 619 | +} |
| 620 | + |
| 621 | +QString vertexShaderSibling(const QString& fsPath) noexcept |
| 622 | +{ |
| 623 | + return shaderSibling(fsPath, {QLatin1String{"vert"}, QLatin1String{"vs"}}); |
| 624 | +} |
| 625 | + |
| 626 | +QString fragmentShaderSibling(const QString& vsPath) noexcept |
| 627 | +{ |
| 628 | + return shaderSibling(vsPath, {QLatin1String{"frag"}, QLatin1String{"fs"}}); |
| 629 | +} |
| 630 | + |
| 631 | +ShaderFamily shaderFileFamily(const QString& path) noexcept |
| 632 | +{ |
| 633 | + struct |
| 634 | + { |
| 635 | + std::string_view marker; |
| 636 | + ShaderFamily family; |
| 637 | + } static constexpr markers[]{ |
| 638 | + {"\"RAW_RASTER_PIPELINE\"", ShaderFamily::RawRaster}, |
| 639 | + {"\"GEOMETRY_FILTER\"", ShaderFamily::GeometryFilter}, |
| 640 | + {"\"COMPUTE_SHADER\"", ShaderFamily::Compute}, |
| 641 | + {"\"VERTEX_SHADER_ART\"", ShaderFamily::VertexShaderArt}, |
| 642 | + }; |
| 643 | + |
| 644 | + for(const auto& [marker, family] : markers) |
| 645 | + { |
| 646 | + // A fresh QFile per marker: fileContains reads from wherever the device |
| 647 | + // currently is. |
| 648 | + QFile f{path}; |
| 649 | + if(score::fileContains(f, marker)) |
| 650 | + return family; |
| 651 | + } |
| 652 | + return ShaderFamily::Unknown; |
| 653 | +} |
| 654 | + |
| 655 | +QString locateShaderPath(const QString& path, const QObject& process) noexcept |
| 656 | +{ |
| 657 | + if(path.isEmpty()) |
| 658 | + return path; |
| 659 | + |
| 660 | + // Walked here rather than through score::IDocument::documentFromObject, |
| 661 | + // which THROWS for an object that is not in a document -- a process being |
| 662 | + // built for a preview or a preset is not, and this is noexcept. |
| 663 | + for(const QObject* obj = &process; obj; obj = obj->parent()) |
| 664 | + if(auto* doc = qobject_cast<const score::Document*>(obj)) |
| 665 | + return score::locateFilePath(path, doc->context()); |
| 666 | + |
| 667 | + return path; |
| 668 | +} |
| 669 | + |
600 | 670 | ShaderSource |
601 | 671 | programFromISFFragmentShaderPath( |
602 | 672 | const QString& fsFilename, QByteArray fsData, ShaderSource::ProgramType type) |
603 | 673 | { |
604 | | - // ISF works by storing a vertex shader next to the fragment shader. |
605 | | - // Score recognises both the long (.frag/.vert) and short (.fs/.vs) |
606 | | - // extension conventions; pairings are tried independently of the FS |
607 | | - // file's own naming so a `foo.frag` next to `foo.vs` (or `foo.fs` next |
608 | | - // to `foo.vert`) also resolves. Without this, the .vs sibling is |
609 | | - // silently ignored and the descriptor falls back to the ISF default |
610 | | - // vertex shader — which doesn't know about user-declared |
611 | | - // VERTEX_INPUTS, so the consumer renders nothing. |
612 | | - const QString candidates[] = { |
613 | | - QString(fsFilename).replace(".frag", ".vert").replace(".fs", ".vs"), |
614 | | - QString(fsFilename).replace(".frag", ".vs"), |
615 | | - QString(fsFilename).replace(".fs", ".vert"), |
616 | | - }; |
617 | | - |
618 | 674 | // If empty: will be using the ISF's default |
619 | 675 | QByteArray vertexData; |
620 | | - for(const QString& vertexName : candidates) |
| 676 | + if(const QString vertexName = vertexShaderSibling(fsFilename); !vertexName.isEmpty()) |
621 | 677 | { |
622 | | - if(vertexName == fsFilename) |
623 | | - continue; |
624 | | - if(QFile vertexFile{vertexName}; |
625 | | - vertexFile.exists() && vertexFile.open(QIODevice::ReadOnly)) |
626 | | - { |
| 678 | + if(QFile vertexFile{vertexName}; vertexFile.open(QIODevice::ReadOnly)) |
627 | 679 | vertexData = vertexFile.readAll(); |
628 | | - break; |
629 | | - } |
630 | 680 | } |
631 | 681 |
|
632 | 682 | if(fsData.isEmpty()) |
|
0 commit comments