Add fuzzing for transform::VertexPulling
Includes a significant refactoring of helper functions in tint_common_fuzzer.cc/.h BUG=tint:722 Change-Id: I1fdab0113bae02c4a0bf8da0d1b7729f05a2fc5b Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/49902 Commit-Queue: Ryan Harrison <rharrison@chromium.org> Auto-Submit: Ryan Harrison <rharrison@chromium.org> Reviewed-by: Ben Clayton <bclayton@google.com>
This commit is contained in:
parent
4f34e7897b
commit
a0174e613f
|
@ -102,6 +102,11 @@ if (build_with_chromium) {
|
|||
deps = [ ":tint_fuzzer_common" ]
|
||||
}
|
||||
|
||||
fuzzer_test("tint_vertex_pulling_fuzzer") {
|
||||
sources = [ "tint_vertex_pulling_fuzzer.cc" ]
|
||||
deps = [ ":tint_fuzzer_common" ]
|
||||
}
|
||||
|
||||
fuzzer_test("tint_wgsl_reader_spv_writer_fuzzer") {
|
||||
sources = [ "tint_wgsl_reader_spv_writer_fuzzer.cc" ]
|
||||
deps = [ ":tint_fuzzer_common" ]
|
||||
|
@ -196,6 +201,7 @@ if (build_with_chromium) {
|
|||
":tint_renamer_fuzzer",
|
||||
":tint_single_entry_point_fuzzer",
|
||||
":tint_spirv_transform_fuzzer",
|
||||
":tint_vertex_pulling_fuzzer",
|
||||
":tint_wgsl_reader_spv_writer_fuzzer",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ if (${TINT_BUILD_WGSL_READER} AND ${TINT_BUILD_SPV_WRITER})
|
|||
add_tint_fuzzer(tint_renamer_fuzzer)
|
||||
add_tint_fuzzer(tint_single_entry_point_fuzzer)
|
||||
add_tint_fuzzer(tint_spirv_transform_fuzzer)
|
||||
add_tint_fuzzer(tint_vertex_pulling_fuzzer)
|
||||
add_tint_fuzzer(tint_wgsl_reader_spv_writer_fuzzer)
|
||||
endif()
|
||||
|
||||
|
|
|
@ -18,27 +18,17 @@ namespace tint {
|
|||
namespace fuzzers {
|
||||
|
||||
struct Config {
|
||||
const uint8_t* data;
|
||||
size_t size;
|
||||
Config(const uint8_t* data, size_t size) : reader(data, size) {}
|
||||
Reader reader;
|
||||
transform::Manager manager;
|
||||
transform::DataMap inputs;
|
||||
};
|
||||
|
||||
bool AddPlatformIndependentPasses(Config* config) {
|
||||
if (!ExtractFirstIndexOffsetInputs(&config->data, &config->size,
|
||||
&config->inputs)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ExtractBindingRemapperInputs(&config->data, &config->size,
|
||||
&config->inputs)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ExtractSingleEntryPointInputs(&config->data, &config->size,
|
||||
&config->inputs)) {
|
||||
return 0;
|
||||
}
|
||||
ExtractFirstIndexOffsetInputs(&config->reader, &config->inputs);
|
||||
ExtractBindingRemapperInputs(&config->reader, &config->inputs);
|
||||
ExtractSingleEntryPointInputs(&config->reader, &config->inputs);
|
||||
ExtractVertexPullingInputs(&config->reader, &config->inputs);
|
||||
|
||||
config->manager.Add<transform::BoundArrayAccessors>();
|
||||
config->manager
|
||||
|
@ -48,15 +38,14 @@ bool AddPlatformIndependentPasses(Config* config) {
|
|||
config->manager.Add<transform::BindingRemapper>();
|
||||
config->manager.Add<transform::Renamer>();
|
||||
config->manager.Add<tint::transform::SingleEntryPoint>();
|
||||
config->manager.Add<tint::transform::VertexPulling>();
|
||||
|
||||
return true;
|
||||
return !config->reader.failed();
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
{
|
||||
Config config;
|
||||
config.data = data;
|
||||
config.size = size;
|
||||
Config config(data, size);
|
||||
|
||||
if (!AddPlatformIndependentPasses(&config)) {
|
||||
return 0;
|
||||
|
@ -65,14 +54,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
|||
fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv);
|
||||
fuzzer.SetTransformManager(&(config.manager), std::move(config.inputs));
|
||||
|
||||
fuzzer.Run(config.data, config.size);
|
||||
fuzzer.Run(config.reader.data(), config.reader.size());
|
||||
}
|
||||
|
||||
#if TINT_BUILD_HLSL_WRITER
|
||||
{
|
||||
Config config;
|
||||
config.data = data;
|
||||
config.size = size;
|
||||
Config config(data, size);
|
||||
|
||||
if (!AddPlatformIndependentPasses(&config)) {
|
||||
return 0;
|
||||
|
@ -83,15 +70,13 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
|||
fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kHLSL);
|
||||
fuzzer.SetTransformManager(&config.manager, std::move(config.inputs));
|
||||
|
||||
fuzzer.Run(config.data, config.size);
|
||||
fuzzer.Run(config.reader.data(), config.reader.size());
|
||||
}
|
||||
#endif // TINT_BUILD_HLSL_WRITER
|
||||
|
||||
#if TINT_BUILD_MSL_WRITER
|
||||
{
|
||||
Config config;
|
||||
config.data = data;
|
||||
config.size = size;
|
||||
Config config(data, size);
|
||||
|
||||
if (!AddPlatformIndependentPasses(&config)) {
|
||||
return 0;
|
||||
|
@ -102,14 +87,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
|||
fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kMSL);
|
||||
fuzzer.SetTransformManager(&config.manager, std::move(config.inputs));
|
||||
|
||||
fuzzer.Run(config.data, config.size);
|
||||
fuzzer.Run(config.reader.data(), config.reader.size());
|
||||
}
|
||||
#endif // TINT_BUILD_MSL_WRITER
|
||||
#if TINT_BUILD_SPV_WRITER
|
||||
{
|
||||
Config config;
|
||||
config.data = data;
|
||||
config.size = size;
|
||||
Config config(data, size);
|
||||
|
||||
if (!AddPlatformIndependentPasses(&config)) {
|
||||
return 0;
|
||||
|
@ -120,7 +103,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
|||
fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv);
|
||||
fuzzer.SetTransformManager(&config.manager, std::move(config.inputs));
|
||||
|
||||
fuzzer.Run(config.data, config.size);
|
||||
fuzzer.Run(config.reader.data(), config.reader.size());
|
||||
}
|
||||
#endif // TINT_BUILD_SPV_WRITER
|
||||
|
||||
|
|
|
@ -20,8 +20,10 @@ namespace fuzzers {
|
|||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
transform::Manager transform_manager;
|
||||
transform::DataMap transform_inputs;
|
||||
Reader r(data, size);
|
||||
|
||||
if (!ExtractBindingRemapperInputs(&data, &size, &transform_inputs)) {
|
||||
ExtractBindingRemapperInputs(&r, &transform_inputs);
|
||||
if (r.failed()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -30,7 +32,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
|||
fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv);
|
||||
fuzzer.SetTransformManager(&transform_manager, std::move(transform_inputs));
|
||||
|
||||
return fuzzer.Run(data, size);
|
||||
return fuzzer.Run(r.data(), r.size());
|
||||
}
|
||||
|
||||
} // namespace fuzzers
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
namespace tint {
|
||||
namespace fuzzers {
|
||||
|
||||
namespace {
|
||||
|
||||
[[noreturn]] void TintInternalCompilerErrorReporter(
|
||||
const tint::diag::List& diagnostics) {
|
||||
auto printer = tint::diag::Printer::create(stderr, true);
|
||||
|
@ -37,41 +39,72 @@ namespace fuzzers {
|
|||
auto printer = tint::diag::Printer::create(stderr, true);
|
||||
printer->write(
|
||||
"Fuzzing detected valid input program being transformed into an invalid "
|
||||
"output progam",
|
||||
"output progam\n",
|
||||
{diag::Color::kRed, true});
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
bool ExtractBindingRemapperInputs(const uint8_t** data,
|
||||
size_t* size,
|
||||
tint::transform::DataMap* inputs) {
|
||||
if ((*size) < sizeof(uint8_t)) {
|
||||
return false;
|
||||
transform::VertexAttributeDescriptor ExtractVertexAttributeDescriptor(
|
||||
Reader* r) {
|
||||
transform::VertexAttributeDescriptor desc;
|
||||
desc.format = r->enum_class<transform::VertexFormat>(
|
||||
static_cast<uint8_t>(transform::VertexFormat::kLastEntry) + 1);
|
||||
desc.offset = r->read<uint64_t>();
|
||||
desc.shader_location = r->read<uint32_t>();
|
||||
return desc;
|
||||
}
|
||||
|
||||
transform::VertexBufferLayoutDescriptor ExtractVertexBufferLayoutDescriptor(
|
||||
Reader* r) {
|
||||
transform::VertexBufferLayoutDescriptor desc;
|
||||
desc.array_stride = r->read<uint64_t>();
|
||||
desc.step_mode = r->enum_class<transform::InputStepMode>(
|
||||
static_cast<uint8_t>(transform::InputStepMode::kLastEntry) + 1);
|
||||
desc.attributes = r->vector(ExtractVertexAttributeDescriptor);
|
||||
return desc;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reader::Reader(const uint8_t* data, size_t size) : data_(data), size_(size) {}
|
||||
|
||||
std::string Reader::string() {
|
||||
auto count = read<uint8_t>();
|
||||
if (failed_ || size_ < count) {
|
||||
mark_failed();
|
||||
return "";
|
||||
}
|
||||
std::string out(data_, data_ + count);
|
||||
data_ += count;
|
||||
size_ -= count;
|
||||
return out;
|
||||
}
|
||||
|
||||
auto count = *reinterpret_cast<const uint8_t*>(*data);
|
||||
(*data) += sizeof(uint8_t);
|
||||
(*size) -= sizeof(uint8_t);
|
||||
void Reader::mark_failed() {
|
||||
size_ = 0;
|
||||
failed_ = true;
|
||||
}
|
||||
|
||||
void Reader::read(void* out, size_t n) {
|
||||
if (n > size_) {
|
||||
mark_failed();
|
||||
return;
|
||||
}
|
||||
memcpy(&out, data_, n);
|
||||
data_ += n;
|
||||
size_ -= n;
|
||||
}
|
||||
|
||||
void ExtractBindingRemapperInputs(Reader* r, tint::transform::DataMap* inputs) {
|
||||
struct Config {
|
||||
uint32_t old_group;
|
||||
uint32_t old_binding;
|
||||
uint32_t new_group;
|
||||
uint32_t new_binding;
|
||||
uint8_t old_group;
|
||||
uint8_t old_binding;
|
||||
uint8_t new_group;
|
||||
uint8_t new_binding;
|
||||
ast::AccessControl::Access new_ac;
|
||||
};
|
||||
|
||||
if ((*size) < count * sizeof(Config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<Config> configs(count);
|
||||
|
||||
memcpy(configs.data(), *data, count * sizeof(Config));
|
||||
|
||||
(*data) += count * sizeof(Config);
|
||||
(*size) -= count * sizeof(Config);
|
||||
|
||||
std::vector<Config> configs = r->vector<Config>();
|
||||
transform::BindingRemapper::BindingPoints binding_points;
|
||||
transform::BindingRemapper::AccessControls access_controls;
|
||||
for (const auto& config : configs) {
|
||||
|
@ -82,59 +115,33 @@ bool ExtractBindingRemapperInputs(const uint8_t** data,
|
|||
|
||||
inputs->Add<transform::BindingRemapper::Remappings>(binding_points,
|
||||
access_controls);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExtractFirstIndexOffsetInputs(const uint8_t** data,
|
||||
size_t* size,
|
||||
void ExtractFirstIndexOffsetInputs(Reader* r,
|
||||
tint::transform::DataMap* inputs) {
|
||||
struct Config {
|
||||
uint32_t group;
|
||||
uint32_t binding;
|
||||
};
|
||||
|
||||
if ((*size) < sizeof(Config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Config config;
|
||||
memcpy(&config, data, sizeof(config));
|
||||
|
||||
(*data) += sizeof(Config);
|
||||
(*size) -= sizeof(Config);
|
||||
|
||||
Config config = r->read<Config>();
|
||||
inputs->Add<tint::transform::FirstIndexOffset::BindingPoint>(config.binding,
|
||||
config.group);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExtractSingleEntryPointInputs(const uint8_t** data,
|
||||
size_t* size,
|
||||
void ExtractSingleEntryPointInputs(Reader* r,
|
||||
tint::transform::DataMap* inputs) {
|
||||
if ((*size) < sizeof(uint8_t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto count = *reinterpret_cast<const uint8_t*>(*data);
|
||||
(*data) += sizeof(uint8_t);
|
||||
(*size) -= sizeof(uint8_t);
|
||||
|
||||
if ((*size) < count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* c = reinterpret_cast<const char*>(*data);
|
||||
std::string input(c, c + count);
|
||||
|
||||
(*data) += count * sizeof(char);
|
||||
(*size) -= count * sizeof(char);
|
||||
|
||||
std::string input = r->string();
|
||||
transform::SingleEntryPoint::Config cfg(input);
|
||||
inputs->Add<transform::SingleEntryPoint::Config>(cfg);
|
||||
}
|
||||
|
||||
return true;
|
||||
void ExtractVertexPullingInputs(Reader* r, tint::transform::DataMap* inputs) {
|
||||
transform::VertexPulling::Config cfg;
|
||||
cfg.entry_point_name = r->string();
|
||||
cfg.vertex_state = r->vector(ExtractVertexBufferLayoutDescriptor);
|
||||
cfg.pulling_group = r->read<uint32_t>();
|
||||
inputs->Add<transform::VertexPulling::Config>(cfg);
|
||||
}
|
||||
|
||||
CommonFuzzer::CommonFuzzer(InputFormat input, OutputFormat output)
|
||||
|
@ -158,7 +165,6 @@ int CommonFuzzer::Run(const uint8_t* data, size_t size) {
|
|||
#if TINT_BUILD_WGSL_READER
|
||||
case InputFormat::kWGSL: {
|
||||
std::string str(reinterpret_cast<const char*>(data), size);
|
||||
|
||||
file = std::make_unique<Source::File>("test.wgsl", str);
|
||||
program = reader::wgsl::Parse(file.get());
|
||||
break;
|
||||
|
|
|
@ -15,23 +15,80 @@
|
|||
#ifndef FUZZERS_TINT_COMMON_FUZZER_H_
|
||||
#define FUZZERS_TINT_COMMON_FUZZER_H_
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "include/tint/tint.h"
|
||||
|
||||
namespace tint {
|
||||
namespace fuzzers {
|
||||
|
||||
bool ExtractBindingRemapperInputs(const uint8_t** data,
|
||||
size_t* size,
|
||||
tint::transform::DataMap* inputs);
|
||||
bool ExtractFirstIndexOffsetInputs(const uint8_t** data,
|
||||
size_t* size,
|
||||
tint::transform::DataMap* inputs);
|
||||
class Reader {
|
||||
public:
|
||||
Reader(const uint8_t* data, size_t size);
|
||||
|
||||
bool ExtractSingleEntryPointInputs(const uint8_t** data,
|
||||
size_t* size,
|
||||
tint::transform::DataMap* inputs);
|
||||
bool failed() { return failed_; }
|
||||
const uint8_t* data() { return data_; }
|
||||
size_t size() { return size_; }
|
||||
|
||||
template <typename T>
|
||||
T read() {
|
||||
T out{};
|
||||
read(&out, sizeof(T));
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string string();
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> vector() {
|
||||
auto count = read<uint8_t>();
|
||||
if (failed_ || size_ < count) {
|
||||
mark_failed();
|
||||
return {};
|
||||
}
|
||||
std::vector<T> out(count);
|
||||
memcpy(out.data(), data_, count * sizeof(T));
|
||||
data_ += count * sizeof(T);
|
||||
size_ -= count * sizeof(T);
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> vector(T (*extract)(Reader*)) {
|
||||
auto count = read<uint8_t>();
|
||||
if (size_ < count) {
|
||||
mark_failed();
|
||||
return {};
|
||||
}
|
||||
std::vector<T> out(count);
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
out[i] = extract(this);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
template <typename T>
|
||||
T enum_class(uint8_t count) {
|
||||
auto val = read<uint8_t>();
|
||||
return static_cast<T>(val % count);
|
||||
}
|
||||
|
||||
private:
|
||||
void mark_failed();
|
||||
void read(void* out, size_t n);
|
||||
|
||||
const uint8_t* data_;
|
||||
size_t size_;
|
||||
bool failed_ = false;
|
||||
};
|
||||
|
||||
void ExtractBindingRemapperInputs(Reader* r, tint::transform::DataMap* inputs);
|
||||
void ExtractFirstIndexOffsetInputs(Reader* r, tint::transform::DataMap* inputs);
|
||||
|
||||
void ExtractSingleEntryPointInputs(Reader* r, tint::transform::DataMap* inputs);
|
||||
|
||||
void ExtractVertexPullingInputs(Reader* r, tint::transform::DataMap* inputs);
|
||||
|
||||
enum class InputFormat { kWGSL, kSpv, kNone };
|
||||
|
||||
|
|
|
@ -20,8 +20,10 @@ namespace fuzzers {
|
|||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
tint::transform::Manager transform_manager;
|
||||
tint::transform::DataMap transform_inputs;
|
||||
Reader r(data, size);
|
||||
|
||||
if (!ExtractFirstIndexOffsetInputs(&data, &size, &transform_inputs)) {
|
||||
ExtractFirstIndexOffsetInputs(&r, &transform_inputs);
|
||||
if (r.failed()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -30,7 +32,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
|||
tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv);
|
||||
fuzzer.SetTransformManager(&transform_manager, std::move(transform_inputs));
|
||||
|
||||
return fuzzer.Run(data, size);
|
||||
return fuzzer.Run(r.data(), r.size());
|
||||
}
|
||||
|
||||
} // namespace fuzzers
|
||||
|
|
|
@ -20,8 +20,10 @@ namespace fuzzers {
|
|||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
tint::transform::Manager transform_manager;
|
||||
tint::transform::DataMap transform_inputs;
|
||||
Reader r(data, size);
|
||||
|
||||
if (!ExtractSingleEntryPointInputs(&data, &size, &transform_inputs)) {
|
||||
ExtractSingleEntryPointInputs(&r, &transform_inputs);
|
||||
if (r.failed()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -30,7 +32,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
|||
tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv);
|
||||
fuzzer.SetTransformManager(&transform_manager, std::move(transform_inputs));
|
||||
|
||||
return fuzzer.Run(data, size);
|
||||
return fuzzer.Run(r.data(), r.size());
|
||||
}
|
||||
|
||||
} // namespace fuzzers
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// TODO(tint:753): Remove this fuzzer once that transform is only
|
||||
// being used from sanitizers.
|
||||
|
||||
#include "fuzzers/tint_common_fuzzer.h"
|
||||
|
||||
namespace tint {
|
||||
namespace fuzzers {
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
tint::transform::Manager transform_manager;
|
||||
tint::transform::DataMap transform_inputs;
|
||||
Reader r(data, size);
|
||||
|
||||
ExtractVertexPullingInputs(&r, &transform_inputs);
|
||||
if (r.failed()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
transform_manager.Add<tint::transform::VertexPulling>();
|
||||
|
||||
tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv);
|
||||
fuzzer.SetTransformManager(&transform_manager, {});
|
||||
|
||||
return fuzzer.Run(r.data(), r.size());
|
||||
}
|
||||
|
||||
} // namespace fuzzers
|
||||
} // namespace tint
|
|
@ -28,6 +28,7 @@ TINT_INSTANTIATE_TYPEINFO(tint::transform::VertexPulling::Config);
|
|||
|
||||
namespace tint {
|
||||
namespace transform {
|
||||
|
||||
namespace {
|
||||
|
||||
struct State {
|
||||
|
|
|
@ -56,12 +56,13 @@ enum class VertexFormat {
|
|||
kI32,
|
||||
kVec2I32,
|
||||
kVec3I32,
|
||||
kVec4I32
|
||||
kVec4I32,
|
||||
kLastEntry = kVec4I32
|
||||
};
|
||||
|
||||
/// Describes if a vertex attributes increments with vertex index or instance
|
||||
/// index
|
||||
enum class InputStepMode { kVertex, kInstance };
|
||||
enum class InputStepMode { kVertex, kInstance, kLastEntry = kInstance };
|
||||
|
||||
/// Describes a vertex attribute within a buffer
|
||||
struct VertexAttributeDescriptor {
|
||||
|
|
Loading…
Reference in New Issue