Files
EVEngine/test/asset_shader.cpp

124 lines
5.7 KiB
C++

#include "asset/AssetCooker.h"
#include "asset/EvpackResourceReader.h"
#include "asset/ShaderAsset.h"
#include "asset/import/ShaderImporter.h"
#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
using namespace eve;
using namespace eve::asset;
namespace {
Value shaderDefinition() {
auto value = Value::fromJson(R"({"schema":"eve.shader","schemaVersion":1,
"interface":"mesh3d","format":"spirv-1.6",
"vertex":[119734787,67072,0,2,0,327695,0,1,1852399981,0],
"fragment":[119734787,67072,0,2,0,327695,4,1,1852399981,0],
"parameters":[{"name":"strength","default":[1.0]}]})");
REQUIRE(value.ok());
return std::move(value).takeValue();
}
} // namespace
TEST_CASE("asset.shader decodes owning parameter and stage data") {
auto result = decodeShaderAsset(shaderDefinition());
REQUIRE(result.ok());
REQUIRE_EQ(result.value().parameters.size(), std::size_t(1));
REQUIRE_EQ(result.value().parameters[0].name, std::string("strength"));
REQUIRE_EQ(result.value().parameters[0].defaults[0], 1.f);
REQUIRE_EQ(result.value().vertex.size(), std::size_t(10));
}
TEST_CASE("asset.shader rejects future versions unknown fields and wrong stages") {
auto definition = shaderDefinition();
auto& fields = *definition.getIf<Value::Object>();
fields["schemaVersion"] = Value(std::int64_t(2));
REQUIRE(!decodeShaderAsset(definition).ok());
fields["schemaVersion"] = Value(std::int64_t(1));
fields["unexpected"] = Value(true);
REQUIRE(!decodeShaderAsset(definition).ok());
fields.erase("unexpected");
fields["fragment"] = fields["vertex"];
REQUIRE(!decodeShaderAsset(definition).ok());
}
TEST_CASE("asset.shader rejects duplicate parameters and exhausted ABI") {
auto definition = shaderDefinition();
auto& fields = *definition.getIf<Value::Object>();
auto& parameters = *fields["parameters"].getIf<Value::Array>();
parameters.push_back(parameters.front());
REQUIRE(!decodeShaderAsset(definition).ok());
parameters.clear();
for (int i = 0; i < 33; ++i)
parameters.emplace_back(
Value::Object{{"name", Value("p" + std::to_string(i))}, {"default", Value(Value::Array{Value(0.0)})}});
REQUIRE(!decodeShaderAsset(definition).ok());
}
TEST_CASE("asset.shader rejects truncated instructions and byte budgets") {
auto definition = shaderDefinition();
ShaderAssetLimits limits;
limits.maximumStageWords = 9;
REQUIRE(!decodeShaderAsset(definition, limits).ok());
auto& words = *definition.getIf<Value::Object>()->at("vertex").getIf<Value::Array>();
words.pop_back();
REQUIRE(!decodeShaderAsset(definition).ok());
}
TEST_CASE("asset.shader cooks reopens and loads beside Unity schema versions") {
auto id = PersistentId::parse("550e8400-e29b-41d4-a716-446655440000");
REQUIRE(id);
auto reference = AssetRef::parse("asset://550e8400-e29b-41d4-a716-446655440000");
REQUIRE(reference.ok());
auto json = shaderDefinition().toJson();
REQUIRE(json.ok());
EvaArchive source;
source.manifest.packageId = *id;
source.manifest.packageName = "shader.test";
source.manifest.packageVersion = "1.0.0";
const std::string path = "assets/" + id->format() + "/asset.json";
source.manifest.assets.push_back({reference.value(), "eve.shader", SchemaVersion(1), path, {}, {}});
source.manifest.entrypoints.emplace("default", reference.value());
source.entries.push_back({path, {json.value().begin(), json.value().end()}});
auto profile = assetCookProfileForTarget("windows-x86_64-vulkan");
REQUIRE(profile.ok());
auto first = cookEvaToEvpack(source, profile.value());
REQUIRE(first.ok());
auto second = cookEvaToEvpack(source, profile.value());
REQUIRE(second.ok());
REQUIRE_EQ(first.value().bytes, second.value().bytes);
auto package = parseEvpack(first.value().bytes);
REQUIRE(package.ok());
EvpackResourceReader reader(std::make_shared<const Evpack>(std::move(package).takeValue()));
EvpackCapabilities capabilities{"windows", "x86_64", "vulkan", {"bc", "rgba8"}, {"spirv-1.6"}, {"high"}, {}};
auto loaded = loadShaderAsset(reader, reference.value(), capabilities);
REQUIRE(loaded.ok());
REQUIRE_EQ(loaded.value().parameters[0].name, std::string("strength"));
capabilities.graphics = "webgpu";
REQUIRE(!loadShaderAsset(reader, reference.value(), capabilities).ok());
auto web = assetCookProfileForTarget("web-wasm32-webgpu");
REQUIRE(web.ok());
REQUIRE(!cookEvaToEvpack(source, web.value()).ok());
}
TEST_CASE("asset.shader import preserves identity and rejects invalid candidates") {
auto id = PersistentId::parse("018f6f22-2490-7ad2-bf58-4f1dbca31040");
REQUIRE(id);
asset_import::ImportPackageIdentity identity{*id, "shader.test", "1.0.0", {}};
auto definition = shaderDefinition();
auto json = definition.toJson();
REQUIRE(json.ok());
auto first = asset_import::prepareShaderImport(identity, json.value());
REQUIRE(first.ok());
auto second = asset_import::prepareShaderImport(identity, json.value());
REQUIRE(second.ok());
REQUIRE_EQ(first.value().manifest.assets[0].asset, second.value().manifest.assets[0].asset);
REQUIRE_EQ(first.value().manifest.assets[0].asset.id(), id->child("shader:default"));
auto archive = buildEvaArchive(first.value().manifest, first.value().entries);
REQUIRE(archive.ok());
auto reopened = parseEvaArchive(archive.value());
REQUIRE(reopened.ok());
REQUIRE(!asset_import::prepareShaderImport(identity, "{}").ok());
REQUIRE(!asset_import::prepareShaderImport({}, json.value()).ok());
}