chore: import upstream snapshot with attribution
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:14:16 +08:00
commit 8a852e4b4e
36502 changed files with 9277225 additions and 0 deletions
@@ -0,0 +1,245 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
load("//tensorflow/lite:special_rules.bzl", "tflite_portable_test_suite")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)
cc_library(
name = "preprocessor",
srcs = ["preprocessor.cc"],
hdrs = ["preprocessor.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:status",
"@com_google_absl//absl/strings",
],
)
cc_test(
name = "preprocessor_test",
srcs = ["preprocessor_test.cc"],
tags = [
"local",
"tflite_not_portable_ios",
],
deps = [
":preprocessor",
"@com_google_absl//absl/strings:string_view",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "object_accessor",
srcs = ["object_accessor.cc"],
hdrs = ["object_accessor.h"],
deps = [
":preprocessor",
":variable_accessor",
"//tensorflow/lite/delegates/gpu/common:access_type",
"//tensorflow/lite/delegates/gpu/common:data_type",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:object",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/types:variant",
],
)
cc_test(
name = "object_accessor_test",
srcs = ["object_accessor_test.cc"],
tags = [
"local",
],
deps = [
":object_accessor",
":preprocessor",
":variable_accessor",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:object",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/types:variant",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "shader_code",
hdrs = ["shader_code.h"],
deps = [
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:object",
"//tensorflow/lite/delegates/gpu/gl:variable",
],
)
cc_library(
name = "shader_codegen",
srcs = ["shader_codegen.cc"],
hdrs = ["shader_codegen.h"],
deps = [
":compiled_node",
":object_accessor",
":preprocessor",
":shader_code",
":variable_accessor",
"//tensorflow/lite/delegates/gpu/common:gpu_info",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/gl:compiler_options",
"//tensorflow/lite/delegates/gpu/gl:object",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings",
],
)
cc_library(
name = "compiled_node",
srcs = ["compiled_node.cc"],
hdrs = ["compiled_node.h"],
deps = [
":rename",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:object",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/strings",
],
)
cc_test(
name = "compiled_node_test",
srcs = ["compiled_node_test.cc"],
deps = [
":compiled_node",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "fuse_inplace",
srcs = ["fuse_inplace.cc"],
hdrs = ["fuse_inplace.h"],
deps = [
":compiled_node",
":preprocessor",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:model_transformer",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:any",
],
)
cc_library(
name = "fuse_inline",
srcs = ["fuse_inline.cc"],
hdrs = ["fuse_inline.h"],
deps = [
":compiled_node",
":shader_code",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:model_transformer",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:any",
],
)
cc_library(
name = "rename",
srcs = ["rename.cc"],
hdrs = ["rename.h"],
deps = [
":object_accessor",
":preprocessor",
":variable_accessor",
"//tensorflow/lite/delegates/gpu/common:status",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"//tensorflow/lite/delegates/gpu/gl:object",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/strings",
],
)
cc_library(
name = "fuse_auto_input",
srcs = ["fuse_auto_input.cc"],
hdrs = ["fuse_auto_input.h"],
deps = [
":compiled_node",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:model_transformer",
"//tensorflow/lite/delegates/gpu/common:operations",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:any",
"@com_google_absl//absl/types:variant",
],
)
cc_test(
name = "fuse_auto_input_test",
srcs = ["fuse_auto_input_test.cc"],
tags = [
"local",
"no_mac", # TODO(b/171881489)
"no_oss", # TODO(b/171881489)
],
deps = [
":compiled_node",
":fuse_auto_input",
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:model_transformer",
"//tensorflow/lite/delegates/gpu/gl:node_shader",
"@com_google_absl//absl/types:any",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "variable_accessor",
srcs = ["variable_accessor.cc"],
hdrs = ["variable_accessor.h"],
deps = [
":preprocessor",
"//tensorflow/lite/delegates/gpu/common:types",
"//tensorflow/lite/delegates/gpu/gl:variable",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/types:variant",
],
)
cc_test(
name = "variable_accessor_test",
srcs = ["variable_accessor_test.cc"],
tags = [
"local",
"tflite_not_portable_ios",
],
deps = [
":preprocessor",
":variable_accessor",
"//tensorflow/lite/delegates/gpu/common:types",
"@com_google_googletest//:gtest_main",
],
)
tflite_portable_test_suite()
@@ -0,0 +1,69 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/compiler/compiled_node.h"
#include <algorithm>
#include <iterator>
#include <string>
#include "absl/container/flat_hash_set.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/rename.h"
namespace tflite {
namespace gpu {
namespace gl {
absl::Status MergeCode(CompiledNodeAttributes* attr,
CompiledNodeAttributes* merged_attr) {
// build a map of known names.
absl::flat_hash_set<std::string> known_names;
for (const auto& parameter : merged_attr->code.parameters) {
known_names.insert(parameter.name);
}
for (const auto& object : merged_attr->code.objects) {
known_names.insert(object.first);
}
// Rewrite parameters with unique names.
int index =
merged_attr->code.parameters.size() + merged_attr->code.objects.size();
RETURN_IF_ERROR(Rename(
[&](absl::string_view name) -> std::string {
std::string n(name.begin(), name.end());
// Add index to the end of a variable name until it's unique
std::string ret = n;
while (known_names.find(ret) != known_names.end()) {
ret = absl::StrCat(n, index++);
}
known_names.insert(ret);
return ret;
},
&attr->code));
std::move(attr->code.objects.begin(), attr->code.objects.end(),
std::back_inserter(merged_attr->code.objects));
std::move(attr->code.parameters.begin(), attr->code.parameters.end(),
std::back_inserter(merged_attr->code.parameters));
std::move(attr->node_indices.begin(), attr->node_indices.end(),
std::back_inserter(merged_attr->node_indices));
return absl::OkStatus();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,52 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_COMPILED_NODE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_COMPILED_NODE_H_
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
namespace tflite {
namespace gpu {
namespace gl {
// Contains compiler internal attributes for each node after it was processed by
// NodeShader.
struct CompiledNodeAttributes {
std::vector<Object> inputs;
std::vector<Object> outputs;
GeneratedCode code;
// nodes that are covered by the provided shader.
std::vector<NodeId> node_indices;
};
// Moves all code objects, parameters and node indices from attr to merged_attr.
// Parameters and objects in attr.code.source_code are renamed to ensure
// uniqueness.
absl::Status MergeCode(CompiledNodeAttributes* attr,
CompiledNodeAttributes* merged_attr);
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_COMPILED_NODE_H_
@@ -0,0 +1,73 @@
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/compiler/compiled_node.h"
#include <algorithm>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
bool VariableDuplicates(std::vector<Variable> variables) {
std::sort(
std::begin(variables), std::end(variables),
[](const auto& lhs, const auto& rhs) { return lhs.name < rhs.name; });
for (int i = 0; i < variables.size() - 1; ++i) {
if (variables[i].name == variables[i + 1].name) return true;
}
return false;
}
TEST(CompiledNodeTest, NoDuplicates) {
Variable scalar;
scalar.name = "scalar";
Variable scalar1;
scalar1.name = "scalar1";
CompiledNodeAttributes attr;
CompiledNodeAttributes merged_attr;
attr.code.parameters = {scalar, scalar1};
merged_attr.code.parameters = {scalar};
ASSERT_OK(MergeCode(&attr, &merged_attr));
// Check for duplicates
EXPECT_FALSE(VariableDuplicates(merged_attr.code.parameters));
}
TEST(CompiledNodeTest, NameConvergenceConflict) {
Variable scalar;
scalar.name = "scalar";
Variable scalar1;
scalar1.name = "scalar1";
CompiledNodeAttributes attr;
CompiledNodeAttributes merged_attr;
attr.code.parameters = {scalar1, scalar};
merged_attr.code.parameters = {scalar};
ASSERT_OK(MergeCode(&attr, &merged_attr));
// Check for duplicates
EXPECT_FALSE(VariableDuplicates(merged_attr.code.parameters));
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,255 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/compiler/fuse_auto_input.h"
#include <any>
#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_set.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_replace.h"
#include "absl/types/any.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/compiled_node.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
std::pair<std::string, std::string> MakeValueReplacement(int n, int k) {
return {absl::StrCat("value_", n), absl::StrCat("value_", k)};
}
std::pair<std::string, std::string> MakeDataReplacement(int n, int k) {
return {absl::StrCat("input_data_", n), absl::StrCat("input_data_", k)};
}
} // namespace
TransformResult FuseAutoInput::ApplyToNode(Node* node, GraphFloat32* graph) {
auto& node_attr =
std::any_cast<CompiledNodeAttributes&>(node->operation.attributes);
auto& node_code = node_attr.code;
if (node_code.input != IOStructure::AUTO) {
return {TransformStatus::SKIPPED, ""};
}
uint3 workgroup = node_code.workgroup;
auto node_outputs = graph->FindOutputs(node->id);
// Check which inputs could be fused into the current node.
std::vector<std::pair<Node*, int>> nodes_to_fuse;
std::vector<std::pair<ValueId, int>> input_values;
int input_num = -1;
for (auto input_value : graph->FindInputs(node->id)) {
input_num++;
const ValueId input_id = input_value->id;
input_values.push_back({input_id, input_num});
if (graph->FindConsumers(input_id).size() > 1) {
continue; // input is consumed by >1 nodes
}
Node* input_producer = graph->FindProducer(input_id);
if (input_producer == nullptr) {
continue; // graph's input
}
if (graph->FindOutputs(input_producer->id).size() != 1) {
continue; // input node has more than one output
}
auto& input_producer_attr = std::any_cast<const CompiledNodeAttributes&>(
input_producer->operation.attributes);
if (input_producer_attr.code.output != IOStructure::AUTO) {
continue;
}
if (input_producer_attr.code.workload != node_code.workload &&
uint3() != input_producer_attr.code.workload) {
continue;
}
if (input_producer_attr.code.workgroup != uint3()) {
// New fused node should fuse only a single shader that has pre-defined
// workgroup. Such shader is considered "heavy". Do not fuse two heavy
// shaders into one.
// TODO(eignasheva): make sure it still works.
if (workgroup != uint3()) {
continue;
}
workgroup = input_producer_attr.code.workgroup;
}
nodes_to_fuse.push_back({input_producer, input_num});
input_values.pop_back(); // this value will not be used as input.
}
if (nodes_to_fuse.empty()) {
return {TransformStatus::SKIPPED, ""};
}
// Skip fusions which will result in duplicate inputs, e.g. diamond shapes.
{
absl::flat_hash_set<ValueId> all_inputs;
for (const auto& node_to_fuse : nodes_to_fuse) {
for (const auto& input : graph->FindInputs(node_to_fuse.first->id)) {
if (all_inputs.find(input->id) != all_inputs.end()) {
return {TransformStatus::SKIPPED, ""};
}
all_inputs.insert(input->id);
}
}
for (const auto& input : graph->FindInputs(node->id)) {
if (all_inputs.find(input->id) != all_inputs.end()) {
return {TransformStatus::SKIPPED, ""};
}
all_inputs.insert(input->id);
}
}
// Break connections between current node and its inputs.
for (auto value : graph->FindInputs(node->id)) {
if (!graph->RemoveConsumer(node->id, value->id).ok()) {
return {TransformStatus::INVALID, ""};
}
}
std::string operation_type;
std::string source_code;
std::string values;
// Node source code need to be appended later to the end.
std::swap(source_code, node_code.source_code);
// Indicates value_k that is beyond originally declared [0..n] values,
// therefore, it can be used by newly added dependencies.
int extra_input_num = input_num;
input_num = 0;
// Fuse all nodes into one.
for (auto input_and_num : nodes_to_fuse) {
auto& input = input_and_num.first;
auto& attr =
std::any_cast<CompiledNodeAttributes&>(input->operation.attributes);
auto super_inputs = graph->FindInputs(input->id);
// Replace all internal references in the input source code. For example:
// source code "value_0 = max(0, value_0);" will be rewritten into
// "value_2 = max(0, value_2);"
std::vector<std::pair<std::string, std::string>> replacements;
for (int i = 0; i < super_inputs.size(); ++i) {
// Node source code uses value_N to access output value from the fused
// node. Use correct reference.
//
// Here value_N does not correspond to input_N anymore. Instead it tracks
// value_n and input_m independently. Value_index uses an index needed
// for the "final" shader, while input_num preserves the order of inputs.
// For example:
// Shader A: input_0, input_1
// value_0 = value_0 > value_1 ? value_0 : value_1;
//
// Shader B: input_0
// value_0 = max(0, value_0);
//
// AddShader: input_0, input_1
// value_0 = value_0 + value_1;
//
// Fused shader is going to have 3 inputs: input_0 (A), input_1 (A),
// input_2 (B). But Shader B need to store result in value_1, because
// AddShader refers to it as 'value_1'. So, fused shader will look as
// follows:
//
// // Shader A
// vec4 value_0 = input_data_0.data[gid.x, gid.y, gid.z];
// vec4 value_2 = input_data_1.data[gid.x, gid.y, gid.z];
// value_0 = value_0 > value_2 ? value_0 : value_2;
//
// // Shader B
// vec4 value_1 = input_data_2.data[gid.x, gid.y, gid.z];
// value_1 = max(0, value_1);
//
// // AddShader
// value_0 = value_0 + value_1;
//
// output_data_0.data[gid.x, gid.y, gid.z] = value_0;
int value_index = i == 0 ? input_and_num.second : ++extra_input_num;
replacements.push_back(MakeValueReplacement(i, value_index));
replacements.push_back(MakeDataReplacement(i, input_num));
// Declare input values based on the input structure of the merged node.
// This code copies what shader_codegen would do automatically.
if (attr.code.input == IOStructure::AUTO) {
absl::StrAppend(&values, " value_", value_index, " = $input_data_",
input_num, "[gid.x, gid.y, gid.z]$;\n");
}
if (!graph->AddConsumer(node->id, super_inputs[i]->id).ok()) {
return {TransformStatus::INVALID, ""};
}
input_num++;
}
// Also rename all _h and _w parameters to the new names.
for (auto& param : attr.code.parameters) {
param.name = absl::StrReplaceAll(param.name, replacements);
}
attr.code.source_code =
absl::StrReplaceAll(attr.code.source_code, replacements);
// Merge all objects, parameters and source code.
if (!MergeCode(&attr, &node_attr).ok()) {
return {TransformStatus::INVALID, "Unable to merge the code"};
}
absl::StrAppend(&node_attr.code.source_code, "{\n", attr.code.source_code,
"\n}");
if (!operation_type.empty()) {
operation_type += ",";
}
operation_type += input->operation.type;
if (!graph->DeleteNode(input->id).ok()) {
return {TransformStatus::INVALID, ""};
}
}
// Add back all inputs that are used directly by the fused node.
for (int i = 0; i < input_values.size(); i++) {
if (node_code.input == IOStructure::AUTO) {
absl::StrAppend(&values, " value_", input_values[i].second,
" = $input_data_", input_num,
"[gid.x, gid.y, gid.z]$;\n");
}
if (!graph->AddConsumer(node->id, input_values[i].first).ok()) {
return {TransformStatus::INVALID, ""};
}
input_num++;
}
node_code.input = IOStructure::ONLY_DEFINITIONS;
absl::StrAppend(&node->operation.type, "(", operation_type, ")");
node_code.source_code =
absl::StrCat(values, node_code.source_code, "{//FUSED",
node->operation.type, "\n", source_code, "\n}");
return {TransformStatus::APPLIED, ""};
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,49 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_FUSE_AUTO_INPUT_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_FUSE_AUTO_INPUT_H_
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
namespace tflite {
namespace gpu {
namespace gl {
// Fuses nodes that have auto output with auto input node using the following
// rules.
//
// Source graph:
// A B C
// \ | /
// D
//
// - A, B and C each have a single output marked as AUTO
// - Each output is used only by D
// - D has all inputs marked as AUTO
//
// Result: in the best case a single node that does (A,B,C)+D operations.
//
class FuseAutoInput : public NodeTransformation {
public:
TransformResult ApplyToNode(Node* node, GraphFloat32* graph) final;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_FUSE_AUTO_INPUT_H_
@@ -0,0 +1,105 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/compiler/fuse_auto_input.h"
#include <utility>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/types/any.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/compiled_node.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(FuseAutoInputTest, SkipsDiamond) {
// v0
// / \
// n1 n2
// v1 v2
// \ /
// n3
// v3
GraphFloat32 graph;
auto* v0 = graph.NewValue();
auto* v1 = graph.NewValue();
auto* v2 = graph.NewValue();
auto* v3 = graph.NewValue();
auto* n1 = graph.NewNode();
CompiledNodeAttributes a1;
a1.code.output = IOStructure::AUTO;
n1->operation.attributes = std::move(a1);
ASSERT_OK(graph.AddConsumer(n1->id, v0->id));
ASSERT_OK(graph.SetProducer(n1->id, v1->id));
auto* n2 = graph.NewNode();
CompiledNodeAttributes a2;
a2.code.output = IOStructure::AUTO;
n2->operation.attributes = std::move(a2);
ASSERT_OK(graph.AddConsumer(n2->id, v0->id));
ASSERT_OK(graph.SetProducer(n2->id, v2->id));
auto* n3 = graph.NewNode();
CompiledNodeAttributes a3;
a3.code.input = IOStructure::AUTO;
n3->operation.attributes = std::move(a3);
ASSERT_OK(graph.AddConsumer(n3->id, v1->id));
ASSERT_OK(graph.AddConsumer(n3->id, v2->id));
ASSERT_OK(graph.SetProducer(n3->id, v3->id));
FuseAutoInput fuse_auto_input;
EXPECT_EQ(fuse_auto_input.ApplyToNode(n3, &graph).status,
TransformStatus::SKIPPED);
}
TEST(FuseAutoInputTest, SkipsTriangle) {
// v0
// | \
// | n1
// | v1
// | /
// n2
// v2
GraphFloat32 graph;
auto* v0 = graph.NewValue();
auto* v1 = graph.NewValue();
auto* v2 = graph.NewValue();
auto* n1 = graph.NewNode();
CompiledNodeAttributes a1;
a1.code.output = IOStructure::AUTO;
n1->operation.attributes = std::move(a1);
ASSERT_OK(graph.AddConsumer(n1->id, v0->id));
ASSERT_OK(graph.SetProducer(n1->id, v1->id));
auto* n2 = graph.NewNode();
CompiledNodeAttributes a2;
a2.code.input = IOStructure::AUTO;
n2->operation.attributes = std::move(a2);
ASSERT_OK(graph.AddConsumer(n2->id, v0->id));
ASSERT_OK(graph.AddConsumer(n2->id, v1->id));
ASSERT_OK(graph.SetProducer(n2->id, v2->id));
FuseAutoInput fuse_auto_input;
EXPECT_EQ(fuse_auto_input.ApplyToNode(n2, &graph).status,
TransformStatus::SKIPPED);
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,78 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/compiler/fuse_inline.h"
#include <any>
#include <string>
#include <vector>
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/types/any.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/compiled_node.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
TransformResult FuseAutoOutputWithInline::ApplyToNodesSequence(
const std::vector<Node*>& sequence, GraphFloat32* graph) {
Node* node1 = sequence.front();
Node* node2 = sequence.back();
auto& attr1 =
std::any_cast<CompiledNodeAttributes&>(node1->operation.attributes);
auto& attr2 =
std::any_cast<CompiledNodeAttributes&>(node2->operation.attributes);
if (attr1.code.output != IOStructure::AUTO ||
graph->FindInputs(node2->id).size() != 1 ||
graph->FindOutputs(node2->id).size() != 1 ||
attr2.code.output != IOStructure::AUTO ||
attr2.code.input != IOStructure::AUTO ||
(attr1.code.workload != attr2.code.workload &&
uint3() != attr2.code.workload) ||
graph->FindOutputs(node1->id).size() !=
graph->FindInputs(node2->id).size()) {
return {TransformStatus::SKIPPED, ""};
}
// Check if the code was not fused yet, and wrap source code into {}.
if (!absl::StrContains(node1->operation.type, '+')) {
attr1.code.source_code =
absl::StrCat("\n{\n", attr1.code.source_code, "\n}\n");
}
if (!MergeCode(&attr2, &attr1).ok()) {
return {TransformStatus::INVALID, "Unable to merge two nodes"};
}
absl::StrAppend(&attr1.code.source_code, "{\n", attr2.code.source_code,
"\n}");
node1->operation.type += "+" + node2->operation.type;
if (!RemoveFollowingNode(graph, node2, node1).ok()) {
return {TransformStatus::INVALID,
"Unable to remove node " + std::to_string(node2->id)};
}
return {TransformStatus::APPLIED, ""};
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,57 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_FUSE_INLINE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_FUSE_INLINE_H_
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
namespace tflite {
namespace gpu {
namespace gl {
// Fuses every two nodes where first node does default output and second node
// is INLINE.
//
// Generates code as follows:
// 1. all uniforms are inlined
// 2. source code is wrapped into {}
// For example:
// value = clamp(value, 0.0, clip);
// +
// value = 1.0 / (1.0 + exp(-1.0 * value));
// will turn into:
// {
// value = clamp(value, 0.0, clip);
// }
// {
// value = 1.0 / (1.0 + exp(-1.0 * value));
// }
class FuseAutoOutputWithInline : public SequenceTransformation {
public:
int ExpectedSequenceLength() const final { return 2; }
TransformResult ApplyToNodesSequence(const std::vector<Node*>& sequence,
GraphFloat32* graph) final;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_FUSE_INLINE_H_
@@ -0,0 +1,154 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/compiler/fuse_inplace.h"
#include <any>
#include <cstring>
#include <string>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/string_view.h"
#include "absl/types/any.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/compiled_node.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
static const char* kInplacePrefix = "inplace_update:\0";
class EmptyInplaceRewrite : public InlineRewrite {
public:
RewriteStatus Rewrite(absl::string_view input, std::string* output) final {
if (input.compare(0, strlen(kInplacePrefix), kInplacePrefix) == 0) {
num_rewrites_++;
return RewriteStatus::SUCCESS;
}
return RewriteStatus::NOT_RECOGNIZED;
}
int num_rewrites() const { return num_rewrites_; }
private:
int num_rewrites_ = 0;
};
// Takes a code as an input. Replaces 'value_0' in the code with a value that
// comes in a rewrite. For example:
// code: value_0 = max(value_0, 0);
// rewrite: inplace_update:result_12 -> result_12 = max(result_12, 0);
//
class InplaceCodeRewrite : public InlineRewrite {
public:
explicit InplaceCodeRewrite(const std::string& code) : code_(code) {}
RewriteStatus Rewrite(absl::string_view input, std::string* output) final {
int len = strlen(kInplacePrefix);
if (input.compare(0, len, kInplacePrefix) == 0) {
auto variable_name = input.substr(len);
absl::StrAppend(output,
absl::StrReplaceAll(code_, {{"value_0", variable_name}}));
return RewriteStatus::SUCCESS;
}
return RewriteStatus::NOT_RECOGNIZED;
}
private:
std::string code_;
};
} // namespace
TransformResult RemoveUnusedInplaceUpdates::ApplyToNode(Node* node,
GraphFloat32* graph) {
auto& attr =
std::any_cast<CompiledNodeAttributes&>(node->operation.attributes);
// Remove inplace block by rewriting to empty string.
EmptyInplaceRewrite rewrite;
TextPreprocessor preprocessor('$', true);
preprocessor.AddRewrite(&rewrite);
if (!preprocessor.Rewrite(attr.code.source_code, &attr.code.source_code)
.ok()) {
return {TransformStatus::INVALID, ""};
}
return {rewrite.num_rewrites() > 0 ? TransformStatus::APPLIED
: TransformStatus::SKIPPED,
""};
}
TransformResult FuseInplaceUpdate::ApplyToNodesSequence(
const std::vector<Node*>& sequence, GraphFloat32* graph) {
Node* node1 = sequence.front();
Node* node2 = sequence.back();
auto& attr1 =
std::any_cast<CompiledNodeAttributes&>(node1->operation.attributes);
auto& attr2 =
std::any_cast<CompiledNodeAttributes&>(node2->operation.attributes);
if (graph->FindInputs(node2->id).size() != 1 ||
graph->FindOutputs(node2->id).size() != 1 ||
attr2.code.output != IOStructure::AUTO ||
attr2.code.input != IOStructure::AUTO ||
(attr1.code.workload != attr2.code.workload &&
uint3() != attr2.code.workload)) {
return {TransformStatus::SKIPPED, ""};
}
// First count of replaces that would happen to check whether rewrite is
// needed.
{
EmptyInplaceRewrite counting_rewrite;
TextPreprocessor preprocessor('$', true);
preprocessor.AddRewrite(&counting_rewrite);
std::string temp;
if (!preprocessor.Rewrite(attr1.code.source_code, &temp).ok()) {
return {TransformStatus::INVALID, ""};
}
// no rewrites in the source code. skip it.
if (counting_rewrite.num_rewrites() == 0) {
return {TransformStatus::SKIPPED, ""};
}
}
if (!MergeCode(&attr2, &attr1).ok()) {
return {TransformStatus::INVALID, "Unable to merge two nodes"};
}
TextPreprocessor preprocessor('$', true);
InplaceCodeRewrite rewrite(attr2.code.source_code);
preprocessor.AddRewrite(&rewrite);
if (!preprocessor.Rewrite(attr1.code.source_code, &attr1.code.source_code)
.ok()) {
return {TransformStatus::INVALID, ""};
}
node1->operation.type += "+" + node2->operation.type;
if (!RemoveFollowingNode(graph, node2, node1).ok()) {
return {TransformStatus::INVALID,
"Unable to remove node " + std::to_string(node2->id)};
}
return {TransformStatus::APPLIED, ""};
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,67 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_FUSE_INPLACE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_FUSE_INPLACE_H_
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
namespace tflite {
namespace gpu {
namespace gl {
// Fuse two shaders where second shader is inline shader with the first.
// First shader should have a special symbol that defines a place where such
// fusion should be made and what variable needs to be changed.
// Second shader needs to operation with 'value_0' variable.
// Example:
//
// First shader:
// vec4 result = input_data_0.data[gid.x, gid.y, gid.z];
// $inplace_update:result$
// ...
// output_data_0.data[1,2,3] = result;
//
// Second shader:
// value_0 = max(value_0, 0);
//
// Fused shader:
// vec4 result = input_data_0.data[gid.x, gid.y, gid.z];
// result = max(result, 0);
// ...
// output_data_0.data[1,2,3] = result;
//
class FuseInplaceUpdate : public SequenceTransformation {
public:
int ExpectedSequenceLength() const final { return 2; }
TransformResult ApplyToNodesSequence(const std::vector<Node*>& sequence,
GraphFloat32* graph) final;
};
// Removes all %inplace_update:XXX% strings from the code.
class RemoveUnusedInplaceUpdates : public NodeTransformation {
public:
TransformResult ApplyToNode(Node* node, GraphFloat32* graph) final;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_FUSE_INPLACE_H_
@@ -0,0 +1,624 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/compiler/object_accessor.h"
#include <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/common/access_type.h"
#include "tensorflow/lite/delegates/gpu/common/data_type.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/variable_accessor.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace object_accessor_internal {
// Splits name[index1, index2...] into 'name' and {'index1', 'index2'...}.
IndexedElement ParseElement(absl::string_view input) {
auto i = input.find('[');
if (i == std::string::npos || input.back() != ']') {
return {};
}
return {input.substr(0, i),
absl::StrSplit(input.substr(i + 1, input.size() - i - 2), ',',
absl::SkipWhitespace())};
}
} // namespace object_accessor_internal
namespace {
void MaybeConvertToHalf(DataType data_type, absl::string_view value,
std::string* output) {
if (data_type == DataType::FLOAT16) {
absl::StrAppend(output, "Vec4ToHalf(", value, ")");
} else {
absl::StrAppend(output, value);
}
}
void MaybeConvertFromHalf(DataType data_type, absl::string_view value,
std::string* output) {
if (data_type == DataType::FLOAT16) {
absl::StrAppend(output, "Vec4FromHalf(", value, ")");
} else {
absl::StrAppend(output, value);
}
}
struct ReadFromTextureGenerator {
RewriteStatus operator()(size_t) const {
if (element.indices.size() != 1) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
// 1D textures are emulated as 2D textures
if (sampler_textures) {
absl::StrAppend(result, "texelFetch(", element.object_name, ", ivec2(",
element.indices[0], ", 0), 0)");
} else {
absl::StrAppend(result, "imageLoad(", element.object_name, ", ivec2(",
element.indices[0], ", 0))");
}
return RewriteStatus::SUCCESS;
}
template <typename Shape>
RewriteStatus operator()(const Shape&) const {
if (element.indices.size() != Shape::size()) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
if (sampler_textures) {
absl::StrAppend(result, "texelFetch(", element.object_name, ", ivec",
Shape::size(), "(", absl::StrJoin(element.indices, ", "),
"), 0)");
} else {
absl::StrAppend(result, "imageLoad(", element.object_name, ", ivec",
Shape::size(), "(", absl::StrJoin(element.indices, ", "),
"))");
}
return RewriteStatus::SUCCESS;
}
const object_accessor_internal::IndexedElement& element;
const bool sampler_textures;
std::string* result;
};
struct ReadFromBufferGenerator {
RewriteStatus operator()(size_t) const {
if (element.indices.size() != 1) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
MaybeConvertFromHalf(
data_type,
absl::StrCat(element.object_name, ".data[", element.indices[0], "]"),
result);
return RewriteStatus::SUCCESS;
}
RewriteStatus operator()(const uint2& size) const {
if (element.indices.size() == 1) {
// access by linear index. Use method above to generate accessor.
return (*this)(1U);
}
if (element.indices.size() != 2) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
MaybeConvertFromHalf(
data_type,
absl::StrCat(element.object_name, ".data[", element.indices[0], " + $",
element.object_name, "_w$ * (", element.indices[1], ")]"),
result);
*requires_sizes = true;
return RewriteStatus::SUCCESS;
}
RewriteStatus operator()(const uint3& size) const {
if (element.indices.size() == 1) {
// access by linear index. Use method above to generate accessor.
return (*this)(1U);
}
if (element.indices.size() != 3) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
MaybeConvertFromHalf(
data_type,
absl::StrCat(element.object_name, ".data[", element.indices[0], " + $",
element.object_name, "_w$ * (", element.indices[1], " + $",
element.object_name, "_h$ * (", element.indices[2], "))]"),
result);
*requires_sizes = true;
return RewriteStatus::SUCCESS;
}
DataType data_type;
const object_accessor_internal::IndexedElement& element;
std::string* result;
// indicates that generated code accessed _w and/or _h index variables.
bool* requires_sizes;
};
// Generates code for reading an element from an object.
RewriteStatus GenerateReadAccessor(
const Object& object,
const object_accessor_internal::IndexedElement& element,
bool sampler_textures, std::string* result, bool* requires_sizes) {
switch (object.object_type) {
case ObjectType::BUFFER:
return std::visit(ReadFromBufferGenerator{object.data_type, element,
result, requires_sizes},
object.size);
case ObjectType::TEXTURE:
return std::visit(
ReadFromTextureGenerator{element, sampler_textures, result},
object.size);
case ObjectType::UNKNOWN:
return RewriteStatus::ERROR;
}
}
struct WriteToBufferGenerator {
RewriteStatus operator()(size_t) const {
if (element.indices.size() != 1) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
absl::StrAppend(result, element.object_name, ".data[", element.indices[0],
"] = ");
MaybeConvertToHalf(data_type, value, result);
return RewriteStatus::SUCCESS;
}
RewriteStatus operator()(const uint2& size) const {
if (element.indices.size() == 1) {
// access by linear index. Use method above to generate accessor.
return (*this)(1U);
}
if (element.indices.size() != 2) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
absl::StrAppend(result, element.object_name, ".data[", element.indices[0],
" + $", element.object_name, "_w$ * (", element.indices[1],
")] = ");
MaybeConvertToHalf(data_type, value, result);
*requires_sizes = true;
return RewriteStatus::SUCCESS;
}
RewriteStatus operator()(const uint3& size) const {
if (element.indices.size() == 1) {
// access by linear index. Use method above to generate accessor.
return (*this)(1U);
}
if (element.indices.size() != 3) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
absl::StrAppend(result, element.object_name, ".data[", element.indices[0],
" + $", element.object_name, "_w$ * (", element.indices[1],
" + $", element.object_name, "_h$ * (", element.indices[2],
"))] = ");
MaybeConvertToHalf(data_type, value, result);
*requires_sizes = true;
return RewriteStatus::SUCCESS;
}
DataType data_type;
const object_accessor_internal::IndexedElement& element;
absl::string_view value;
std::string* result;
// indicates that generated code accessed _w and/or _h index variables.
bool* requires_sizes;
};
struct WriteToTextureGenerator {
RewriteStatus operator()(size_t) const {
if (element.indices.size() != 1) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
// 1D textures are emulated as 2D textures
absl::StrAppend(result, "imageStore(", element.object_name, ", ivec2(",
element.indices[0], ", 0), ", value, ")");
return RewriteStatus::SUCCESS;
}
template <typename Shape>
RewriteStatus operator()(const Shape&) const {
if (element.indices.size() != Shape::size()) {
result->append("WRONG_NUMBER_OF_INDICES");
return RewriteStatus::ERROR;
}
absl::StrAppend(result, "imageStore(", element.object_name, ", ivec",
Shape::size(), "(", absl::StrJoin(element.indices, ", "),
"), ", value, ")");
return RewriteStatus::SUCCESS;
}
const object_accessor_internal::IndexedElement& element;
absl::string_view value;
std::string* result;
};
// Generates code for writing value an element in an object.
RewriteStatus GenerateWriteAccessor(
const Object& object,
const object_accessor_internal::IndexedElement& element,
absl::string_view value, std::string* result, bool* requires_sizes) {
switch (object.object_type) {
case ObjectType::BUFFER:
return std::visit(WriteToBufferGenerator{object.data_type, element, value,
result, requires_sizes},
object.size);
case ObjectType::TEXTURE:
return std::visit(WriteToTextureGenerator{element, value, result},
object.size);
case ObjectType::UNKNOWN:
return RewriteStatus::ERROR;
}
}
std::string ToAccessModifier(AccessType access, bool use_readonly_modifier) {
switch (access) {
case AccessType::READ:
return use_readonly_modifier ? " readonly" : "";
case AccessType::WRITE:
return " writeonly";
case AccessType::READ_WRITE:
return " restrict";
}
return " unknown_access";
}
std::string ToBufferType(DataType data_type) {
switch (data_type) {
case DataType::UINT8:
case DataType::UINT16:
case DataType::UINT32:
return "uvec4";
case DataType::UINT64:
return "u64vec4_not_available_in_glsl";
case DataType::INT8:
case DataType::INT16:
case DataType::INT32:
return "ivec4";
case DataType::INT64:
return "i64vec4_not_available_in_glsl";
case DataType::FLOAT16:
return "uvec2";
case DataType::BOOL:
case DataType::FLOAT32:
return "vec4";
case DataType::FLOAT64:
return "dvec4";
case DataType::UNKNOWN:
return "unknown_buffer_type";
// Do NOT add `default:'; we want build failure for new enum values.
}
}
struct TextureImageTypeGetter {
std::string operator()(size_t) const {
// 1D textures are emulated as 2D textures
return (*this)(uint2());
}
std::string operator()(const uint2&) const {
switch (type) {
case DataType::UINT16:
case DataType::UINT32:
return "uimage2D";
case DataType::INT16:
case DataType::INT32:
return "iimage2D";
case DataType::FLOAT16:
case DataType::FLOAT32:
return "image2D";
default:
return "unknown_image_2d";
}
}
std::string operator()(const uint3&) const {
switch (type) {
case DataType::UINT16:
case DataType::UINT32:
return "uimage2DArray";
case DataType::INT16:
case DataType::INT32:
return "iimage2DArray";
case DataType::FLOAT16:
case DataType::FLOAT32:
return "image2DArray";
default:
return "unknown_image_2d_array";
}
}
DataType type;
};
struct TextureSamplerTypeGetter {
std::string operator()(size_t) const {
// 1D textures are emulated as 2D textures
return (*this)(uint2());
}
std::string operator()(const uint2&) const {
switch (type) {
case DataType::FLOAT16:
case DataType::FLOAT32:
return "sampler2D";
case DataType::INT32:
case DataType::INT16:
return "isampler2D";
case DataType::UINT32:
case DataType::UINT16:
return "usampler2D";
default:
return "unknown_sampler2D";
}
}
std::string operator()(const uint3&) const {
switch (type) {
case DataType::FLOAT16:
case DataType::FLOAT32:
return "sampler2DArray";
case DataType::INT32:
case DataType::INT16:
return "isampler2DArray";
case DataType::UINT32:
case DataType::UINT16:
return "usampler2DArray";
default:
return "unknown_sampler2DArray";
}
}
DataType type;
};
std::string ToImageType(const Object& object, bool sampler_textures) {
if (sampler_textures && (object.access == AccessType::READ)) {
return std::visit(TextureSamplerTypeGetter{object.data_type}, object.size);
} else {
return std::visit(TextureImageTypeGetter{object.data_type}, object.size);
}
}
std::string ToImageLayoutQualifier(DataType type) {
switch (type) {
case DataType::UINT16:
return "rgba16ui";
case DataType::UINT32:
return "rgba32ui";
case DataType::INT16:
return "rgba16i";
case DataType::INT32:
return "rgba32i";
case DataType::FLOAT16:
return "rgba16f";
case DataType::FLOAT32:
return "rgba32f";
default:
return "unknown_image_layout";
}
}
std::string ToImagePrecision(DataType type) {
switch (type) {
case DataType::UINT16:
case DataType::INT16:
case DataType::FLOAT16:
return "mediump";
case DataType::UINT32:
case DataType::INT32:
case DataType::FLOAT32:
return "highp";
default:
return "unknown_image_precision";
}
}
struct SizeParametersAdder {
void operator()(size_t) const {}
void operator()(const uint2& size) const {
variable_accessor->AddUniformParameter(
{absl::StrCat(object_name, "_w"), static_cast<int32_t>(size.x)});
}
// p1 and p2 are padding. For some reason buffer does not map correctly
// without it.
void operator()(const uint3& size) const {
variable_accessor->AddUniformParameter(
{absl::StrCat(object_name, "_w"), static_cast<int32_t>(size.x)});
variable_accessor->AddUniformParameter(
{absl::StrCat(object_name, "_h"), static_cast<int32_t>(size.y)});
}
absl::string_view object_name;
VariableAccessor* variable_accessor;
};
// Adds necessary parameters to parameter accessor that represent object size
// needed for indexed access.
// - 1D : empty
// - 2D : 'int object_name_w'
// - 3D : 'int object_name_w' + 'int object_name_h'
void AddSizeParameters(absl::string_view object_name, const Object& object,
VariableAccessor* parameters) {
std::visit(SizeParametersAdder{object_name, parameters}, object.size);
}
void GenerateObjectDeclaration(absl::string_view name, const Object& object,
std::string* declaration, bool is_mali,
bool sampler_textures) {
switch (object.object_type) {
case ObjectType::BUFFER:
// readonly modifier used to fix shader compilation for Mali on Android 8,
// see b/111601761
absl::StrAppend(declaration, "layout(binding = ", object.binding, ")",
ToAccessModifier(object.access, !is_mali), " buffer B",
object.binding, " { ", ToBufferType(object.data_type),
" data[]; } ", name, ";\n");
break;
case ObjectType::TEXTURE:
if (sampler_textures && (object.access == AccessType::READ)) {
absl::StrAppend(declaration, "layout(binding = ", object.binding,
") uniform ", ToImagePrecision(object.data_type), " ",
ToImageType(object, sampler_textures), " ", name,
";\n");
} else {
absl::StrAppend(
declaration, "layout(", ToImageLayoutQualifier(object.data_type),
", binding = ", object.binding, ")",
ToAccessModifier(object.access, true), " uniform ",
ToImagePrecision(object.data_type), " ",
ToImageType(object, sampler_textures), " ", name, ";\n");
}
break;
case ObjectType::UNKNOWN:
// do nothing.
break;
}
}
} // namespace
RewriteStatus ObjectAccessor::Rewrite(absl::string_view input,
std::string* output) {
// Splits 'a =b' into {'a','b'}.
std::pair<absl::string_view, absl::string_view> n =
absl::StrSplit(input, absl::MaxSplits('=', 1), absl::SkipWhitespace());
if (n.first.empty()) {
return RewriteStatus::NOT_RECOGNIZED;
}
if (n.second.empty()) {
return RewriteRead(absl::StripAsciiWhitespace(n.first), output);
}
return RewriteWrite(absl::StripAsciiWhitespace(n.first),
absl::StripAsciiWhitespace(n.second), output);
}
RewriteStatus ObjectAccessor::RewriteRead(absl::string_view location,
std::string* output) {
auto element = object_accessor_internal::ParseElement(location);
if (element.object_name.empty()) {
return RewriteStatus::NOT_RECOGNIZED;
}
auto it = name_to_object_.find(
std::string(element.object_name.data(), element.object_name.size()));
if (it == name_to_object_.end()) {
return RewriteStatus::NOT_RECOGNIZED;
}
bool requires_sizes = false;
auto status = GenerateReadAccessor(it->second, element, sampler_textures_,
output, &requires_sizes);
if (requires_sizes) {
AddSizeParameters(it->first, it->second, variable_accessor_);
}
return status;
}
RewriteStatus ObjectAccessor::RewriteWrite(absl::string_view location,
absl::string_view value,
std::string* output) {
// name[index1, index2...] = value
auto element = object_accessor_internal::ParseElement(location);
if (element.object_name.empty()) {
return RewriteStatus::NOT_RECOGNIZED;
}
auto it = name_to_object_.find(
std::string(element.object_name.data(), element.object_name.size()));
if (it == name_to_object_.end()) {
return RewriteStatus::NOT_RECOGNIZED;
}
bool requires_sizes = false;
auto status = GenerateWriteAccessor(it->second, element, value, output,
&requires_sizes);
if (requires_sizes) {
AddSizeParameters(it->first, it->second, variable_accessor_);
}
return status;
}
bool ObjectAccessor::AddObject(const std::string& name, Object object) {
if (object.object_type == ObjectType::UNKNOWN) {
return false;
}
return name_to_object_.insert({name, std::move(object)}).second;
}
std::string ObjectAccessor::GetObjectDeclarations() const {
std::string declarations;
for (auto& o : name_to_object_) {
GenerateObjectDeclaration(o.first, o.second, &declarations, is_mali_,
sampler_textures_);
}
return declarations;
}
std::string ObjectAccessor::GetFunctionsDeclarations() const {
// If there is a single object SSBO with F16, then we need to output macros
// as well.
for (const auto& o : name_to_object_) {
if (o.second.data_type == DataType::FLOAT16 &&
o.second.object_type == ObjectType::BUFFER) {
return absl::StrCat(
"#define Vec4FromHalf(v) vec4(unpackHalf2x16(v.x), "
"unpackHalf2x16(v.y))\n",
"#define Vec4ToHalf(v) uvec2(packHalf2x16(v.xy), "
"packHalf2x16(v.zw))");
}
}
return "";
}
std::vector<Object> ObjectAccessor::GetObjects() const {
std::vector<Object> objects;
objects.reserve(name_to_object_.size());
for (auto& o : name_to_object_) {
objects.push_back(o.second);
}
return objects;
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,114 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_OBJECT_ACCESSOR_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_OBJECT_ACCESSOR_H_
#include <map>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/variable_accessor.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
namespace tflite {
namespace gpu {
namespace gl {
// This rewrite handles access to objects both reads and writes.
//
// The following syntax is supported to access objects:
//
// READ:
// vec4 value = $data[i]$;
// where data is a buffer or 1D texture
// vec4 value = $data[i,j]$;
// where data is 2D texture
// vec4 value = $data[i,j,k]$;
// where data is 3D texture
//
// WRITE:
// $data[i] = value$;
// where data is a buffer or 1D texture
// $data[i,j] = value$;
// where data is 2D texture
// $data[i,j,k] = value$;
// where data is 3D texture
//
// Accessor supports all types (gvecN) as well as float16.
//
// TODO(akulik): support field in data[x,y,z].x
//
class ObjectAccessor : public InlineRewrite {
public:
ObjectAccessor(bool is_mali, VariableAccessor* variable_accessor)
: ObjectAccessor(is_mali, /*sampler_textures=*/false, variable_accessor) {
}
ObjectAccessor(bool is_mali, bool sampler_textures,
VariableAccessor* variable_accessor)
: is_mali_(is_mali),
sampler_textures_(sampler_textures),
variable_accessor_(variable_accessor) {}
RewriteStatus Rewrite(absl::string_view input, std::string* output) final;
// Return true if object was successfully added.
bool AddObject(const std::string& name, Object object);
// Returns objects declarations that need to be added in a shader's code.
std::string GetObjectDeclarations() const;
// Returns functions declarations that need to be added in a shader's code.
// These functions are used by code accessing objects.
std::string GetFunctionsDeclarations() const;
// Returns a collection of registered objects
std::vector<Object> GetObjects() const;
private:
RewriteStatus RewriteRead(absl::string_view location, std::string* output);
RewriteStatus RewriteWrite(absl::string_view location,
absl::string_view value, std::string* output);
std::map<std::string, Object> name_to_object_;
const bool is_mali_;
const bool sampler_textures_;
VariableAccessor* variable_accessor_;
};
// Implementation details below.
namespace object_accessor_internal {
// Refers to an element in an object.
struct IndexedElement {
absl::string_view object_name;
std::vector<absl::string_view> indices;
};
// Splits name[index1, index2...] into 'name' and {'index1', 'index2'...}.
IndexedElement ParseElement(absl::string_view input);
} // namespace object_accessor_internal
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_OBJECT_ACCESSOR_H_
@@ -0,0 +1,209 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/compiler/object_accessor.h"
#include <string>
#include <variant>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/variable_accessor.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
struct ParameterComparator {
template <typename T>
bool operator()(const T& t) const {
const T* v = std::get_if<T>(&p.value);
return v && t == *v;
}
const Variable& p;
};
// partially equal
bool operator==(const Variable& l, const Variable& r) {
return l.name == r.name && std::visit(ParameterComparator{l}, r.value);
}
namespace {
TEST(Preprocessor, CornerCases) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
std::string result;
ASSERT_EQ(accessor.Rewrite("", &result), RewriteStatus::NOT_RECOGNIZED);
ASSERT_EQ(accessor.Rewrite("=", &result), RewriteStatus::NOT_RECOGNIZED);
}
TEST(Preprocessor, ReadFromBuffer) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(
accessor.AddObject("obj", MakeReadonlyBuffer(std::vector<float>{1.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite("obj[i]", &result), RewriteStatus::SUCCESS);
EXPECT_TRUE(variable_accessor.GetUniformParameters().empty());
ASSERT_EQ(result, "obj.data[i]");
}
TEST(Preprocessor, ReadFromBufferLinear) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(accessor.AddObject(
"obj", MakeReadonlyBuffer(uint3(1, 2, 3), std::vector<float>{1.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite("obj[i]", &result), RewriteStatus::SUCCESS);
EXPECT_TRUE(variable_accessor.GetUniformParameters().empty());
ASSERT_EQ(result, "obj.data[i]");
}
TEST(Preprocessor, ReadFromBufferByIndex) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(accessor.AddObject(
"obj", MakeReadonlyBuffer(uint3(1, 2, 3), std::vector<float>{1.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite("obj[x,y + 5,z]", &result),
RewriteStatus::SUCCESS);
EXPECT_THAT(variable_accessor.GetUniformParameters(),
testing::UnorderedElementsAre(Variable{"obj_w", 1},
Variable{"obj_h", 2}));
ASSERT_EQ(result, "obj.data[x + $obj_w$ * (y + 5 + $obj_h$ * (z))]");
}
TEST(Preprocessor, ReadFromTexture) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(accessor.AddObject(
"obj", MakeReadonlyTexture(uint3(1, 2, 3), {1.0, 2.0, 3.0, 4.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite("obj[i,j,k]", &result), RewriteStatus::SUCCESS);
// textures don't need extra variables to be stored for indexed access
EXPECT_TRUE(variable_accessor.GetUniformParameters().empty());
ASSERT_EQ(result, "imageLoad(obj, ivec3(i, j, k))");
}
TEST(Preprocessor, ReadFromTexture1D) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(
accessor.AddObject("obj", MakeReadonlyTexture({1.0, 2.0, 3.0, 4.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite("obj[i]", &result), RewriteStatus::SUCCESS);
EXPECT_TRUE(variable_accessor.GetUniformParameters().empty());
ASSERT_EQ(result, "imageLoad(obj, ivec2(i, 0))");
}
TEST(Preprocessor, WriteToBuffer) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(
accessor.AddObject("obj", MakeReadonlyBuffer(std::vector<float>{1.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite(" obj[i] =value", &result),
RewriteStatus::SUCCESS);
EXPECT_TRUE(variable_accessor.GetUniformParameters().empty());
ASSERT_EQ(result, "obj.data[i] = value");
}
TEST(Preprocessor, WriteToBufferByIndex) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(accessor.AddObject(
"obj", MakeReadonlyBuffer(uint3(1, 2, 3), {1.0, 2.0, 3.0, 4.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite(" obj[i,j,k] =value", &result),
RewriteStatus::SUCCESS);
EXPECT_THAT(variable_accessor.GetUniformParameters(),
testing::UnorderedElementsAre(Variable{"obj_w", 1},
Variable{"obj_h", 2}));
ASSERT_EQ(result, "obj.data[i + $obj_w$ * (j + $obj_h$ * (k))] = value");
}
TEST(Preprocessor, WriteToTexture) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(accessor.AddObject(
"obj", MakeReadonlyTexture(uint3(1, 1, 1), {1.0, 2.0, 3.0, 4.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite("obj[i,j,k]= value ", &result),
RewriteStatus::SUCCESS);
ASSERT_EQ(result, "imageStore(obj, ivec3(i, j, k), value)");
}
TEST(Preprocessor, WriteToTexture1D) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(
accessor.AddObject("obj", MakeReadonlyTexture({1.0, 2.0, 3.0, 4.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite("obj[i]= value ", &result),
RewriteStatus::SUCCESS);
EXPECT_TRUE(variable_accessor.GetUniformParameters().empty());
ASSERT_EQ(result, "imageStore(obj, ivec2(i, 0), value)");
}
TEST(Preprocessor, FailedWriteToBuffer) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(
accessor.AddObject("obj", MakeReadonlyBuffer(std::vector<float>{1.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite(" obj[i,j] =value", &result),
RewriteStatus::ERROR);
ASSERT_EQ(result, "WRONG_NUMBER_OF_INDICES");
}
TEST(Preprocessor, FailedWriteToTexture) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(accessor.AddObject(
"obj", MakeReadonlyTexture(uint3(1, 1, 1), {1.0, 2.0, 3.0, 4.0})));
std::string result;
EXPECT_EQ(accessor.Rewrite("obj[i]= value ", &result), RewriteStatus::ERROR);
ASSERT_EQ(result, "WRONG_NUMBER_OF_INDICES");
}
TEST(Preprocessor, DeclareTexture) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(false, &variable_accessor);
ASSERT_TRUE(accessor.AddObject(
"obj", MakeReadonlyTexture(uint3(1, 1, 1), {1.0, 2.0, 3.0, 4.0})));
ASSERT_EQ(accessor.GetObjectDeclarations(),
"layout(rgba32f, binding = 0) readonly uniform highp image2DArray "
"obj;\n");
}
TEST(Preprocessor, DeclareBuffer) {
VariableAccessor variable_accessor(/*inline_values=*/false);
ObjectAccessor accessor(true, &variable_accessor);
ASSERT_TRUE(
accessor.AddObject("obj", MakeReadonlyBuffer(std::vector<float>{1.0})));
ASSERT_EQ(accessor.GetObjectDeclarations(),
"layout(binding = 0) buffer B0 { vec4 data[]; } obj;\n");
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,100 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include <cstddef>
#include <string>
#include <utility>
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
// Given input string and a delimiter returns back a substring including
// delimiters. If there was only starting delimiter found, returns single char.
absl::string_view FindInlineBlock(absl::string_view s, char delimiter) {
size_t start = s.find(delimiter);
if (start != absl::string_view::npos) {
size_t end = s.find(delimiter, start + 1);
if (end != std::string::npos) {
return s.substr(start, end - start + 1);
}
// Special case to indicate that we didn't find the end.
return s.substr(start, 1);
}
return s.substr(s.size(), 0);
}
// For the given 's' and its substring 'subs' returns new substring of 's' that
// begins past 'subs'.
absl::string_view PastSubstr(absl::string_view s, absl::string_view subs) {
return s.substr(subs.data() + subs.size() - s.data());
}
} // namespace
absl::Status TextPreprocessor::Rewrite(const std::string& input,
std::string* output) {
absl::string_view s = input;
std::string result;
while (true) {
absl::string_view inline_block = FindInlineBlock(s, inline_delimiter_);
result.append(s.data(), inline_block.data() - s.data());
if (inline_block.empty()) {
break;
}
if (inline_block.size() == 1) {
return absl::NotFoundError("Unable to find end of inline block");
}
s = PastSubstr(s, inline_block);
bool processed = false;
for (auto& rewrite : inline_rewrites_) {
if (processed) {
break;
}
switch (rewrite->Rewrite(inline_block.substr(1, inline_block.size() - 2),
&result)) {
case RewriteStatus::NOT_RECOGNIZED:
// try another rewrite.
break;
case RewriteStatus::SUCCESS:
processed = true;
break;
case RewriteStatus::ERROR:
return absl::InternalError(absl::StrCat("Error while rewriting '",
inline_block, "': ", result));
}
}
if (!processed) {
if (!keep_unknown_rewrites_) {
return absl::NotFoundError(absl::StrCat(
"Didn't find inline rewrite for '", inline_block, "'"));
}
absl::StrAppend(&result, inline_block);
}
}
*output = std::move(result);
return absl::OkStatus();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,74 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_PREPROCESSOR_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_PREPROCESSOR_H_
#include <memory>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
namespace tflite {
namespace gpu {
namespace gl {
enum class RewriteStatus {
SUCCESS = 0,
NOT_RECOGNIZED = 1,
ERROR = 2,
};
// Inline rewrite matches a string and rewrites it.
class InlineRewrite {
public:
virtual ~InlineRewrite() = default;
virtual RewriteStatus Rewrite(absl::string_view input,
std::string* output) = 0;
};
// Text preprocessor runs a collection of registered rewrites.
// It uses a single character prefix as inline delimiter that needs to quote
// text to be rewritten.
class TextPreprocessor {
public:
// @param keep_unknown_rewrites if true, will keep unhandled rewrites as is
// instead of reporting an error.
TextPreprocessor(char inline_delimiter, bool keep_unknown_rewrites)
: inline_delimiter_(inline_delimiter),
keep_unknown_rewrites_(keep_unknown_rewrites) {}
void AddRewrite(InlineRewrite* rewrite) {
inline_rewrites_.push_back(rewrite);
}
// input and output may point to the same object.
absl::Status Rewrite(const std::string& input, std::string* output);
private:
const char inline_delimiter_;
const bool keep_unknown_rewrites_;
std::vector<InlineRewrite*> inline_rewrites_;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_PREPROCESSOR_H_
@@ -0,0 +1,130 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include <string>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/strings/string_view.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
class AccuInlineRewrite : public InlineRewrite {
public:
explicit AccuInlineRewrite(std::vector<std::string>* blocks)
: blocks_(blocks) {}
RewriteStatus Rewrite(absl::string_view input, std::string* output) final {
blocks_->push_back(std::string(input.data(), input.size()));
output->append("r:");
output->append(input.data(), input.size());
return RewriteStatus::SUCCESS;
}
std::vector<std::string>* blocks_;
};
std::vector<std::string> ParseInlines(const std::string& text) {
std::vector<std::string> blocks;
TextPreprocessor preprocessor('$', false);
AccuInlineRewrite rewrite(&blocks);
preprocessor.AddRewrite(&rewrite);
std::string discard;
preprocessor.Rewrite(text, &discard).IgnoreError();
return blocks;
}
TEST(Preprocessor, CornerCases) {
EXPECT_THAT(ParseInlines(""), testing::ElementsAre());
EXPECT_THAT(ParseInlines("text text"), testing::ElementsAre());
EXPECT_THAT(ParseInlines("$$"), testing::ElementsAre(""));
}
TEST(Preprocessor, One) {
EXPECT_THAT(ParseInlines("$text$"), testing::ElementsAre("text"));
EXPECT_THAT(ParseInlines(" $text$ "), testing::ElementsAre("text"));
}
TEST(Preprocessor, More) {
EXPECT_THAT(ParseInlines("Test $inline1$\n$inline2$ test $inline3$ "),
testing::ElementsAre("inline1", "inline2", "inline3"));
}
std::string RewriteInlines(const std::string& text) {
std::vector<std::string> blocks;
TextPreprocessor preprocessor('$', false);
AccuInlineRewrite rewrite(&blocks);
preprocessor.AddRewrite(&rewrite);
std::string out;
preprocessor.Rewrite(text, &out).IgnoreError();
return out;
}
TEST(Preprocessor, RewriteCornerCases) {
EXPECT_EQ(RewriteInlines(""), "");
EXPECT_EQ(RewriteInlines("text text"), "text text");
EXPECT_EQ(RewriteInlines("$$"), "r:");
}
TEST(Preprocessor, RewriteOne) {
EXPECT_EQ(RewriteInlines("$text$"), "r:text");
EXPECT_EQ(RewriteInlines(" $text$ "), " r:text ");
}
TEST(Preprocessor, RewriteMore) {
EXPECT_EQ(RewriteInlines("Test $inline1$\n$inline2$ test $inline3$ "),
"Test r:inline1\nr:inline2 test r:inline3 ");
}
class SingleRewrite : public InlineRewrite {
public:
RewriteStatus Rewrite(absl::string_view input, std::string* output) final {
if (input == "foo") {
output->append("bla");
return RewriteStatus::SUCCESS;
}
return RewriteStatus::NOT_RECOGNIZED;
}
std::vector<std::string>* blocks_;
};
TEST(Preprocessor, KeepUnknownRewrites) {
TextPreprocessor preprocessor('$', true);
SingleRewrite rewrite;
preprocessor.AddRewrite(&rewrite);
std::string out;
ASSERT_TRUE(preprocessor.Rewrite("Good morning, $name$! $foo$", &out).ok());
EXPECT_EQ("Good morning, $name$! bla", out);
}
TEST(Preprocessor, KeepUnknownRewrites_Fail) {
TextPreprocessor preprocessor('$', false);
SingleRewrite rewrite;
preprocessor.AddRewrite(&rewrite);
std::string out;
EXPECT_FALSE(preprocessor.Rewrite("Good morning, $name$! $foo$", &out).ok());
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,206 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/compiler/rename.h"
#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/object_accessor.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/variable_accessor.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
// Rewrites names of all variables according to returned values from the
// given NameFunctor.
class VariableRewriter : public InlineRewrite {
public:
VariableRewriter(const std::string& inline_delimiter,
const NameFunctor& name_func)
: inline_delimiter_(inline_delimiter), name_func_(name_func) {}
RewriteStatus Rewrite(absl::string_view input, std::string* output) final {
auto ref = variable_accessor_internal::Parse(input);
if (ref.name.empty()) {
absl::StrAppend(output, "INVALID_SYNTAX");
return RewriteStatus::ERROR;
}
auto it =
name_to_variable_.find(std::string(ref.name.data(), ref.name.size()));
if (it == name_to_variable_.end()) {
return RewriteStatus::NOT_RECOGNIZED;
}
// reconstruct access using the new name.
absl::StrAppend(output, inline_delimiter_, it->second.name);
if (!ref.index.empty()) {
absl::StrAppend(output, "[", ref.index, "]");
}
absl::StrAppend(output, ref.field, inline_delimiter_);
return RewriteStatus::SUCCESS;
}
// Return true if variable was successfully added.
bool AddVariable(Variable&& variable) {
std::string old_name = variable.name;
variable.name = name_func_(old_name);
return name_to_variable_.insert({old_name, std::move(variable)}).second;
}
// Returns a collection of uniform parameters with updated names.
std::vector<Variable> GetUniformParameters() const {
std::vector<Variable> variables;
variables.reserve(name_to_variable_.size());
for (const auto& variable : name_to_variable_) {
variables.push_back(variable.second);
}
return variables;
}
private:
const std::string inline_delimiter_;
const NameFunctor name_func_;
absl::flat_hash_map<std::string, Variable> name_to_variable_;
};
// Rewrites names of all objects according to returned values from the
// given NameFunctor.
class ObjectRewriter : public InlineRewrite {
public:
ObjectRewriter(const std::string& inline_delimiter,
const NameFunctor& name_func)
: inline_delimiter_(inline_delimiter), name_func_(name_func) {}
RewriteStatus Rewrite(absl::string_view input, std::string* output) final {
// Splits 'a = b' into {'a','b'}.
std::pair<absl::string_view, absl::string_view> n =
absl::StrSplit(input, absl::MaxSplits('=', 1), absl::SkipWhitespace());
if (n.first.empty()) {
return RewriteStatus::NOT_RECOGNIZED;
}
if (n.second.empty()) {
return RewriteRead(absl::StripAsciiWhitespace(n.first), output);
}
return RewriteWrite(absl::StripAsciiWhitespace(n.first),
absl::StripAsciiWhitespace(n.second), output);
}
// Return true if an object was successfully added.
bool AddObject(const std::string& name, Object object) {
std::string new_name = name_func_(name);
return name_to_object_.insert({name, {new_name, std::move(object)}}).second;
}
// Returns a collection of registered objects with updated names.
std::vector<std::pair<std::string, Object>> GetObjects() const {
std::vector<std::pair<std::string, Object>> objects;
objects.reserve(name_to_object_.size());
for (const auto& o : name_to_object_) {
objects.push_back(o.second);
}
return objects;
}
private:
RewriteStatus RewriteRead(absl::string_view location, std::string* output) {
auto element = object_accessor_internal::ParseElement(location);
if (element.object_name.empty()) {
absl::StrAppend(output, "UNABLE_TO_PARSE_INDEXED_ELEMENT");
return RewriteStatus::ERROR;
}
auto it = name_to_object_.find(
std::string(element.object_name.data(), element.object_name.size()));
if (it == name_to_object_.end()) {
return RewriteStatus::NOT_RECOGNIZED;
}
absl::StrAppend(output, inline_delimiter_, it->second.first, "[",
absl::StrJoin(element.indices, ","), "]",
inline_delimiter_);
return RewriteStatus::SUCCESS;
}
RewriteStatus RewriteWrite(absl::string_view location,
absl::string_view value, std::string* output) {
// name[index1, index2...] = value
auto element = object_accessor_internal::ParseElement(location);
if (element.object_name.empty()) {
absl::StrAppend(output, "UNABLE_TO_PARSE_INDEXED_ELEMENT");
return RewriteStatus::ERROR;
}
auto it = name_to_object_.find(
std::string(element.object_name.data(), element.object_name.size()));
if (it == name_to_object_.end()) {
return RewriteStatus::NOT_RECOGNIZED;
}
absl::StrAppend(output, inline_delimiter_, it->second.first, "[",
absl::StrJoin(element.indices, ","), "] = ", value,
inline_delimiter_);
return RewriteStatus::SUCCESS;
}
const std::string inline_delimiter_;
const NameFunctor name_func_;
absl::flat_hash_map<std::string, std::pair<std::string, Object>>
name_to_object_;
};
} // namespace
absl::Status Rename(const NameFunctor& name_func, GeneratedCode* code) {
VariableRewriter variable_rewriter("$", name_func);
ObjectRewriter object_rewriter("$", name_func);
for (auto&& uniform_parameter : code->parameters) {
if (!variable_rewriter.AddVariable(std::move(uniform_parameter))) {
return absl::InternalError("Variable name already exists");
}
}
for (auto&& object : code->objects) {
if (!object_rewriter.AddObject(object.first, std::move(object.second))) {
return absl::InternalError("Object name already exists");
}
}
TextPreprocessor preprocessor('$', /*keep_unknown_rewrites=*/true);
preprocessor.AddRewrite(&variable_rewriter);
preprocessor.AddRewrite(&object_rewriter);
std::string source_code;
RETURN_IF_ERROR(preprocessor.Rewrite(code->source_code, &source_code));
code->source_code = source_code;
code->parameters = variable_rewriter.GetUniformParameters();
code->objects = object_rewriter.GetObjects();
return absl::OkStatus();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,41 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_RENAME_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_RENAME_H_
#include <functional>
#include <string>
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
namespace tflite {
namespace gpu {
namespace gl {
// Functor takes old name and returns new name.
using NameFunctor = std::function<std::string(absl::string_view name)>;
// Rewrites source code, objects and parameters with the new names supplied
// by the given functor.
absl::Status Rename(const NameFunctor& name_func, GeneratedCode* code);
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_RENAME_H_
@@ -0,0 +1,68 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_SHADER_CODE_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_SHADER_CODE_H_
#include <string>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
struct ShaderCode {
ShaderCode() = default;
ShaderCode(const std::vector<Variable>& in_parameters,
const std::vector<Object>& in_objects, const uint3& in_workload,
const uint3& in_recommended_workgroup,
const std::string& in_source_code,
const std::vector<NodeId>& in_node_indices)
: parameters(in_parameters),
objects(in_objects),
workload(in_workload),
recommended_workgroup(in_recommended_workgroup),
source_code(in_source_code),
node_indices(in_node_indices) {}
// A list of uniform parameters to be set.
std::vector<Variable> parameters;
// A list of objects to bind to opengl program.
std::vector<Object> objects;
uint3 workload;
// operation may specify recommended workgroup size
uint3 recommended_workgroup;
// Generated source code does not set local size, therefore it needs to be set
// elsewhere.
std::string source_code;
// nodes of the graph that are covered by the shader.
std::vector<NodeId> node_indices;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_SHADER_CODE_H_
@@ -0,0 +1,204 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/compiler/shader_codegen.h"
#include <algorithm>
#include <string>
#include <utility>
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "tensorflow/lite/delegates/gpu/common/gpu_info.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/variable_accessor.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
#ifdef __ANDROID__
#include <sys/system_properties.h>
#endif // __ANDROID__
namespace tflite {
namespace gpu {
namespace gl {
ShaderCodegen::ShaderCodegen(const CompilationOptions& options,
const GpuInfo& gpu_info)
: options_(options),
gpu_type_(gpu_info.vendor),
inline_parameters_(options.inline_parameters) {
#ifdef __ANDROID__
if (gpu_info.IsAdreno() &&
gpu_info.adreno_info.adreno_gpu == AdrenoGpu::kAdreno730) {
char sdk_version[PROP_VALUE_MAX];
__system_property_get("ro.build.version.sdk", sdk_version);
if (!strcmp(sdk_version, "31")) inline_parameters_ = false;
} else if (gpu_info.IsPowerVR() &&
!gpu_info.powervr_info.IsBetterThan(PowerVRGpu::kRogueGm9xxx)) {
inline_parameters_ = false;
}
#endif // __ANDROID__
}
absl::Status ShaderCodegen::Build(CompiledNodeAttributes attr,
ShaderCode* shader_code) const {
VariableAccessor variable_accessor(inline_parameters_,
options_.vulkan_support);
ObjectAccessor object_accessor(gpu_type_ == GpuVendor::kMali,
options_.sampler_textures, &variable_accessor);
const auto add_object = [&](const std::string& name, Object&& object) {
if (!object_accessor.AddObject(name, std::forward<Object>(object))) {
return absl::AlreadyExistsError(absl::StrCat("Object \"", name, "\""));
}
return absl::OkStatus();
};
const auto add_uniform_parameter = [&](Variable&& variable) {
const std::string name = variable.name;
const Variable& const_ref = variable;
if (variable_accessor.IsEmptyVariableLength(const_ref)) {
return absl::InvalidArgumentError(
absl::StrCat("Empty uniform vector value \"", name, "\""));
}
if (!variable_accessor.AddUniformParameter(std::move(variable))) {
return absl::AlreadyExistsError(
absl::StrCat("Uniform parameter \"", name, "\""));
}
return absl::OkStatus();
};
for (auto&& object : attr.code.objects) {
RETURN_IF_ERROR(add_object(object.first, std::move(object.second)));
}
for (auto&& variable : attr.code.shared_variables) {
const std::string name = variable.name;
if (!variable_accessor.AddSharedVariable(std::move(variable))) {
return absl::AlreadyExistsError(
absl::StrCat("Shared variable \"", name, "\""));
}
}
for (auto&& variable : attr.code.parameters) {
RETURN_IF_ERROR(add_uniform_parameter(std::move(variable)));
}
int index = 0;
for (auto&& input : attr.inputs) {
RETURN_IF_ERROR(
add_object(absl::StrCat("input_data_", index++), std::move(input)));
}
index = 0;
for (auto&& output : attr.outputs) {
RETURN_IF_ERROR(
add_object(absl::StrCat("output_data_", index++), std::move(output)));
}
// TODO(akulik): workload params need to go away and be replaced with
// output_data_0_w
RETURN_IF_ERROR(add_uniform_parameter(
{"workload_x", static_cast<int32_t>(attr.code.workload.x)}));
RETURN_IF_ERROR(add_uniform_parameter(
{"workload_y", static_cast<int32_t>(attr.code.workload.y)}));
RETURN_IF_ERROR(add_uniform_parameter(
{"workload_z", static_cast<int32_t>(attr.code.workload.z)}));
// NOTE: If the shader has shared variables it will have to use barriers,
// which will conflict with a return at this stage.
// Let the user deal with the geometry constraints.
const bool has_shared_variables = !attr.code.shared_variables.empty();
std::string main_source_code = has_shared_variables ? R"(
ivec3 gid = ivec3(gl_GlobalInvocationID.xyz);
)"
: R"(
ivec3 gid = ivec3(gl_GlobalInvocationID.xyz);
if (gid.x >= $workload_x$ || gid.y >= $workload_y$ || gid.z >= $workload_z$) {
return;
}
)";
switch (attr.code.input) {
case IOStructure::ONLY_DEFINITIONS:
for (int i = 0; i < attr.inputs.size(); ++i) {
absl::StrAppend(&main_source_code, " highp vec4 value_", i,
" = vec4(0);\n");
}
break;
case IOStructure::AUTO: {
for (int i = 0; i < attr.inputs.size(); ++i) {
absl::StrAppend(&main_source_code, " highp vec4 value_", i,
" = $input_data_", i, "[gid.x, gid.y, gid.z]$;\n");
}
break;
}
}
main_source_code.append(attr.code.source_code);
if (attr.code.output == IOStructure::AUTO) {
for (int i = 0; i < attr.outputs.size(); ++i) {
absl::StrAppend(&main_source_code, " $output_data_", i,
"[gid.x, gid.y, gid.z] = value_", i, "$;\n");
}
}
// At this point main function is already generated. Now we need to process
// object and variable accessors.
// process objects first. Object accessor may introduce new uniform
// parameters that need to be rewritten in the subsequent pass.
{
TextPreprocessor preprocessor('$', /*keep_unknown_rewrites=*/true);
preprocessor.AddRewrite(&object_accessor);
RETURN_IF_ERROR(preprocessor.Rewrite(main_source_code, &main_source_code));
}
{
TextPreprocessor preprocessor('$', /*keep_unknown_rewrites=*/false);
preprocessor.AddRewrite(&variable_accessor);
RETURN_IF_ERROR(preprocessor.Rewrite(main_source_code, &main_source_code));
}
if (inline_parameters_) {
main_source_code = absl::StrCat(variable_accessor.GetConstDeclarations(),
main_source_code);
}
// partial_source_code is only missing the following which is added later:
// #version 310 es
// layout(local_size_x = ..., local_size_y = ..., local_size_z = ...) in;
const char* precision = options_.allow_precision_loss ? "mediump" : "highp";
const std::string partial_source_code = absl::StrCat(
"layout(std430) buffer;\n", //
"precision ", precision, " float;\n", //
object_accessor.GetFunctionsDeclarations(), "\n", //
object_accessor.GetObjectDeclarations(), "\n", //
variable_accessor.GetUniformParameterDeclarations(), "\n", //
variable_accessor.GetSharedVariableDeclarations(), "\n", //
"void main() {\n", //
main_source_code, //
"}");
*shader_code =
ShaderCode(variable_accessor.GetUniformParameters(),
object_accessor.GetObjects(), attr.code.workload,
attr.code.workgroup, partial_source_code, attr.node_indices);
return absl::OkStatus();
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,55 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_SHADER_CODEGEN_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_SHADER_CODEGEN_H_
#include <string>
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/gpu_info.h"
#include "tensorflow/lite/delegates/gpu/common/model.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/compiled_node.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/object_accessor.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/shader_code.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler_options.h"
#include "tensorflow/lite/delegates/gpu/gl/object.h"
namespace tflite {
namespace gpu {
namespace gl {
// This class is responsible for assembling a shader by putting together
// objects, parameters declarations and main function.
class ShaderCodegen {
public:
ShaderCodegen(const CompilationOptions& options, const GpuInfo& gpu_info);
// Builds final program representation.
absl::Status Build(CompiledNodeAttributes attr,
ShaderCode* shader_code) const;
private:
const CompilationOptions options_;
const GpuVendor gpu_type_;
bool inline_parameters_;
};
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_SHADER_CODEGEN_H_
@@ -0,0 +1,557 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/compiler/variable_accessor.h"
#include <array>
#include <cstdint>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace variable_accessor_internal {
// Parse the following regex manually
// name(\[index\])?(\.field)?
VariableReference Parse(absl::string_view input) {
VariableReference ref;
auto start_index = input.find('[');
if (start_index != std::string::npos) {
auto end_index = input.rfind(']');
if (end_index == std::string::npos) {
return ref;
}
ref.index = input.substr(start_index + 1, end_index - start_index - 1);
ref.name = input.substr(0, start_index);
ref.field = input.substr(end_index + 1);
} else {
auto dot = input.find('.');
if (dot != std::string::npos) {
ref.name = input.substr(0, dot);
ref.field = input.substr(dot);
} else {
ref.name = input;
}
}
return ref;
}
} // namespace variable_accessor_internal
namespace {
struct VariableTypeGetter {
std::string operator()(int) const { return "int"; }
std::string operator()(const int2&) const { return "ivec2"; }
std::string operator()(const std::vector<int2>&) const { return "ivec2"; }
std::string operator()(const int4&) const { return "ivec4"; }
std::string operator()(unsigned int) const { return "uint"; }
std::string operator()(const uint4&) const { return "uvec4"; }
std::string operator()(float) const { return "float"; }
std::string operator()(const float2&) const { return "vec2"; }
std::string operator()(const float4&) const { return "vec4"; }
std::string operator()(const std::vector<float4>&) const { return "vec4"; }
};
// Returns GLSL uniform type of the given variable.
std::string GetVariableType(const Variable::ValueType& value) {
return std::visit(VariableTypeGetter(), value);
}
struct LengthGetter {
template <typename T>
int operator()(const T& param) const {
return 1;
}
template <typename T>
int operator()(const std::vector<T>& param) const {
return param.size();
}
};
int GetLength(const Variable::ValueType& value) {
return std::visit(LengthGetter(), value);
}
template <typename T>
void FormatValue(std::string* result, T t) {
absl::StrAppend(result, t);
}
template <>
void FormatValue(std::string* result, float t) {
absl::StrAppend(result, absl::StrFormat("%.9ff", t));
}
// Unfortunately absl::StrJoin with custom formatter requires formatter to use
// string, not std::string. Therefore, due to this compatibility issue data
// needs to be converted to string representation first and then joined.
template <typename T, int N>
std::vector<std::string> ToString(const std::array<T, N>& data) {
std::vector<std::string> result(N);
for (int i = 0; i < N; ++i) {
FormatValue(&result[i], data[i]);
}
return result;
}
struct ConstGenerator {
template <typename T>
void operator()(T t) const {
FormatValue(result, t);
}
template <typename T>
void operator()(const Vec2<T>& v) const {
absl::StrAppend(result, VariableTypeGetter()(v), "(",
absl::StrJoin(ToString<T, 2>(v.data_), ","), ")");
}
template <typename T>
void operator()(const Vec3<T>& v) const {
absl::StrAppend(result, VariableTypeGetter()(v), "(",
absl::StrJoin(ToString<T, 3>(v.data_), ","), ")");
}
template <typename T>
void operator()(const Vec4<T>& v) const {
absl::StrAppend(result, VariableTypeGetter()(v), "(",
absl::StrJoin(ToString<T, 4>(v.data_), ","), ")");
}
template <typename T>
void operator()(const std::vector<T>& v) const {
std::string type = VariableTypeGetter()(v);
absl::StrAppend(result, type, "[", v.size(), "](");
bool first = true;
for (const auto& i : v) {
if (first) {
first = false;
} else {
absl::StrAppend(result, ",");
}
(*this)(i);
}
absl::StrAppend(result, ")");
}
std::string* result;
};
// Appends string representation of a variable value.
void GetValue(const Variable::ValueType& value, std::string* result) {
std::visit(ConstGenerator{result}, value);
}
struct SharedVariableDeclarationGenerator {
template <typename T>
void operator()(const T&) const {
absl::StrAppend(result, "shared highp ", GetVariableType(variable.value),
" ", variable.name, ";\n");
}
template <typename T>
void operator()(const std::vector<T>& v) const {
absl::StrAppend(result, "shared highp ", GetVariableType(variable.value),
" ", variable.name);
if (v.empty()) {
// Normalize the size of the shared array to that of the WorkGroupSize
absl::StrAppend(
result,
"[gl_WorkGroupSize.z * gl_WorkGroupSize.y * gl_WorkGroupSize.x];\n");
} else {
// Use the specified size
absl::StrAppend(result, "[", v.size(), "];\n");
}
}
const Variable& variable;
std::string* result;
};
void GenerateSharedVariableDeclaration(const Variable& variable,
std::string* result) {
std::visit(SharedVariableDeclarationGenerator{variable, result},
variable.value);
}
struct UniformParameterDeclarationGenerator {
template <typename T>
void operator()(const T&) const {
absl::StrAppend(result, "uniform ", GetVariableType(variable.value), " ",
variable.name, ";\n");
}
template <typename T>
void operator()(const std::vector<T>& v) const {
absl::StrAppend(result, "uniform ", GetVariableType(variable.value), " ",
variable.name, "[", v.size(), "];\n");
}
const Variable& variable;
std::string* result;
};
void GenerateUniformParameterDeclaration(const Variable& variable,
std::string* result) {
std::visit(UniformParameterDeclarationGenerator{variable, result},
variable.value);
}
struct VulkanPushConstantGenerator {
template <typename T>
void operator()(const T&) const {
absl::StrAppend(result, " ", GetVariableType(variable.value), " ",
variable.name, ";\n");
}
template <typename T>
void operator()(const std::vector<T>& v) const {
absl::StrAppend(result, " ", GetVariableType(variable.value), " ",
variable.name, "[", v.size(), "];\n");
}
const Variable& variable;
std::string* result;
};
void GenerateVulkanPushConstant(const Variable& variable, std::string* result) {
std::visit(VulkanPushConstantGenerator{variable, result}, variable.value);
}
struct VariableLengthGetter {
template <typename T>
bool operator()(const T&) const {
return false;
}
template <typename T>
bool operator()(const std::vector<T>&) const {
return true;
}
};
struct VulkanConstantGenerator {
template <typename T>
void operator()(const T&) const {
const std::string variable_type = GetVariableType(variable.value);
// Vulkan specialization constants are used for scalar types, all other
// types go in push (uniform) constants.
if (variable_type == "int" || variable_type == "uint" ||
variable_type == "float") {
absl::StrAppend(result, "layout(constant_id = ", *constant_id, ") const ",
variable_type, " ", variable.name, " = ");
// Always set the default values to zero to generate generic cacheable
// shaders.
absl::StrAppend(result, (variable_type == "float" ? "0.0" : "0"), ";\n");
(*constant_id)++;
} else {
non_scalar_variables->push_back(variable);
}
}
template <typename T>
void operator()(const std::vector<T>& v) const {
non_scalar_variables->push_back(variable);
}
const Variable& variable;
int* const constant_id;
std::vector<Variable>* non_scalar_variables;
std::string* result;
};
void GenerateVulkanConstant(const Variable& variable, int* constant_id,
std::vector<Variable>* non_scalar_variables,
std::string* result) {
std::visit(VulkanConstantGenerator{variable, constant_id,
non_scalar_variables, result},
variable.value);
}
class VulkanConstantsProcessor {
public:
void ProcessVulkanConstant(const Variable& variable, std::string* result) {
GenerateVulkanConstant(variable, &constant_id_, &non_scalar_variables_,
result);
}
void GeneratePushConstantsDeclarations(std::string* result) {
if (!non_scalar_variables_.empty()) {
*result += "\nlayout(push_constant) uniform pushConstants {\n";
for (const auto& variable : non_scalar_variables_) {
GenerateVulkanPushConstant(variable, result);
}
*result += "};\n";
}
}
protected:
// Reserve the first three specialization constants slots for the
// workgroup size.
int constant_id_ = 3;
std::vector<Variable> non_scalar_variables_;
};
// Returns true if value is a vector
bool IsVariableLength(const Variable::ValueType& value) {
return std::visit(VariableLengthGetter(), value);
}
enum Field : uint8_t { UNKNOWN = 4, X = 0, Y = 1, Z = 2, W = 3 };
Field ToField(absl::string_view field_name) {
if (field_name.size() == 2 && field_name[0] == '.') {
switch (field_name[1]) {
case 'x':
return Field::X;
case 'y':
return Field::Y;
case 'z':
return Field::Z;
case 'w':
return Field::W;
}
}
return Field::UNKNOWN;
}
struct FieldAccessor {
template <typename T>
void operator()(const T&) const {}
template <typename T>
void operator()(const Vec2<T>& v) const {
FormatValue(result, v[field]);
}
template <typename T>
void operator()(const Vec3<T>& v) const {
FormatValue(result, v[field]);
}
template <typename T>
void operator()(const Vec4<T>& v) const {
FormatValue(result, v[field]);
}
Field field;
std::string* result;
};
// Appends formatted value of the given field.
void GetValue(const Variable::ValueType& value, Field field,
std::string* result) {
std::visit(FieldAccessor{field, result}, value);
}
struct FieldChecker {
// For trivial as well as variable-length types indexed access is not allowed.
template <typename T>
bool operator()(const T&) const {
return false;
}
template <typename T>
bool operator()(const Vec2<T>& v) const {
return field < v.size();
}
template <typename T>
bool operator()(const Vec3<T>& v) const {
return field < v.size();
}
template <typename T>
bool operator()(const Vec4<T>& v) const {
return field < v.size();
}
template <typename T>
bool operator()(const std::vector<T>&) const {
// technically accessing [0] element of an empty vector is UB, but we need
// only type information for this check. Therefore, construct default T and
// use it instead.
T t;
return (*this)(t);
}
Field field;
};
// Returns true if field has field access and field is not out of bounds.
bool HasField(const Variable::ValueType& value, Field field) {
return std::visit(FieldChecker{field}, value);
}
void AssembleAccessor(absl::string_view name, absl::string_view index,
absl::string_view field, std::string* result) {
if (index.empty()) {
absl::StrAppend(result, name, field);
} else {
absl::StrAppend(result, name, "[", index, "]", field);
}
}
} // namespace
RewriteStatus VariableAccessor::Rewrite(absl::string_view input,
std::string* output) {
auto ref = variable_accessor_internal::Parse(input);
if (ref.name.empty()) {
absl::StrAppend(output, "INVALID_SYNTAX");
return RewriteStatus::ERROR;
}
auto it =
name_to_variable_.find(std::string(ref.name.data(), ref.name.size()));
if (it == name_to_variable_.end()) {
// Uniform with this name is not registered.
return RewriteStatus::NOT_RECOGNIZED;
}
const auto& value = it->second.value;
if (!ref.index.empty() && !IsVariableLength(value)) {
// Trying to access variable by index, but it is not variable-length.
absl::StrAppend(output, "INVALID_ACCESS_BY_INDEX");
return RewriteStatus::ERROR;
}
Field f = ToField(ref.field);
if (!ref.field.empty() && !HasField(value, f)) {
// Trying to access a variable by field, but it does not have it.
absl::StrAppend(output, "INVALID_ACCESS_BY_FIELD");
return RewriteStatus::ERROR;
}
// Error checks are complete now.
// All variable-length variables are encoded as-is without inlining.
if (!inline_values_ || IsVariableLength(value)) {
AssembleAccessor(it->second.name, ref.index, ref.field, output);
} else {
// Parameter + field is replaced with field value.
if (f != Field::UNKNOWN) {
GetValue(value, f, output);
} else {
// Parameter is accessed directly.
GetValue(value, output);
}
}
return RewriteStatus::SUCCESS;
}
bool VariableAccessor::AddSharedVariable(Variable&& variable) {
const std::string name = variable.name;
if (!name_to_variable_.insert({name, std::move(variable)}).second) {
return false;
}
shared_variables_.insert(name);
return true;
}
bool VariableAccessor::AddUniformParameter(Variable&& variable) {
const std::string name = variable.name;
if (!name_to_variable_.insert({name, std::move(variable)}).second) {
return false;
}
uniform_parameters_.insert(name);
return true;
}
bool VariableAccessor::IsEmptyVariableLength(const Variable& variable) const {
const auto& value = variable.value;
return IsVariableLength(value) && GetLength(value) == 0;
}
std::string VariableAccessor::GetConstDeclarations() const {
// Variable length variables are declared as const and accessed via variable
// with index.
std::string declarations;
for (const auto& variable : name_to_variable_) {
// Skip shared variables.
const std::string& variable_name = variable.second.name;
if (shared_variables_.find(variable_name) != shared_variables_.end()) {
continue;
}
const auto& value = variable.second.value;
if (IsVariableLength(value)) {
absl::StrAppend(&declarations, "const ", GetVariableType(value), " ",
variable_name, "[] = ");
GetValue(value, &declarations);
absl::StrAppend(&declarations, ";\n");
}
}
return declarations;
}
std::string VariableAccessor::GetSharedVariableDeclarations() const {
std::string declarations;
for (const auto& name : shared_variables_) {
const auto& variable = name_to_variable_.at(name);
GenerateSharedVariableDeclaration(variable, &declarations);
}
return declarations;
}
std::string VariableAccessor::GetUniformParameterDeclarations() const {
std::string declarations;
if (!inline_values_) {
if (vulkan_support_) {
VulkanConstantsProcessor processor;
for (const auto& name : uniform_parameters_) {
const auto& variable = name_to_variable_.at(name);
processor.ProcessVulkanConstant(variable, &declarations);
}
processor.GeneratePushConstantsDeclarations(&declarations);
} else {
for (const auto& name : uniform_parameters_) {
const auto& variable = name_to_variable_.at(name);
GenerateUniformParameterDeclaration(variable, &declarations);
}
}
}
return declarations;
}
std::vector<Variable> VariableAccessor::GetUniformParameters() const {
std::vector<Variable> variables;
if (!inline_values_) {
variables.reserve(name_to_variable_.size());
// Keep the order of the variables consistent with that of the declarations
for (const auto& name : uniform_parameters_) {
const auto& variable = name_to_variable_.at(name);
variables.push_back(variable);
}
}
return variables;
}
} // namespace gl
} // namespace gpu
} // namespace tflite
@@ -0,0 +1,103 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_VARIABLE_ACCESSOR_H_
#define TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_VARIABLE_ACCESSOR_H_
#include <set>
#include <string>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/strings/string_view.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
#include "tensorflow/lite/delegates/gpu/gl/variable.h"
namespace tflite {
namespace gpu {
namespace gl {
// This rewrite handles access to variables. It may rewrite a variable with
// actual values if 'inline_values' is set to true.
//
// The following syntax is supported to access variables:
// - simple variable: name
// - variable with field: name.(x|y|z|w)
// - variable with index: name[i]
// - variable with index and field: name[i].(x|y|z|w)
//
// If 'inline_values' is set to true, non-variable-length variables will be
// inlined. For example, 'base.x' will be replaced with value of 'x' field from
// 'base'. Variable-length variables are declared as const and accessed via
// index. These declarations are returned by GetConstDeclarations.
//
// If 'inline_values' is set to false, all variables will be declared as
// uniforms. Uniform declarations are returned by GetUniformDeclarations.
class VariableAccessor : public InlineRewrite {
public:
explicit VariableAccessor(bool inline_values, bool vulkan_support = false)
: inline_values_(inline_values), vulkan_support_(vulkan_support) {}
RewriteStatus Rewrite(absl::string_view input, std::string* output) final;
// Returns true if variable was successfully added.
bool AddSharedVariable(Variable&& variable);
// Returns true if variable was successfully added.
bool AddUniformParameter(Variable&& variable);
// Returns true if variable value is an empty vector.
bool IsEmptyVariableLength(const Variable& variable) const;
// Returns const variables that need to be inlined in the a shader's code.
std::string GetConstDeclarations() const;
// Returns shared variable declarations that need to be inlined.
std::string GetSharedVariableDeclarations() const;
// Returns uniform parameter declarations that need to be inlined.
std::string GetUniformParameterDeclarations() const;
// Returns a collection of uniform parameters.
std::vector<Variable> GetUniformParameters() const;
private:
const bool inline_values_;
const bool vulkan_support_;
absl::flat_hash_map<std::string, Variable> name_to_variable_;
std::set<std::string> shared_variables_;
std::set<std::string> uniform_parameters_;
};
// Implementation details below.
namespace variable_accessor_internal {
struct VariableReference {
absl::string_view name;
absl::string_view index;
absl::string_view field;
};
// Parse the following regex manually
// name(\[index\])?(\.field)?
VariableReference Parse(absl::string_view input);
} // namespace variable_accessor_internal
} // namespace gl
} // namespace gpu
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_COMPILER_VARIABLE_ACCESSOR_H_
@@ -0,0 +1,101 @@
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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.
==============================================================================*/
#include "tensorflow/lite/delegates/gpu/gl/compiler/variable_accessor.h"
#include <cstdint>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/delegates/gpu/gl/compiler/preprocessor.h"
namespace tflite {
namespace gpu {
namespace gl {
namespace {
TEST(PreprocessorTest, CornerCases) {
VariableAccessor variable_accessor(/*inline_values=*/true);
std::string result;
EXPECT_EQ(variable_accessor.Rewrite("unknown", &result),
RewriteStatus::NOT_RECOGNIZED);
}
TEST(PreprocessorTest, Value) {
VariableAccessor variable_accessor(/*inline_values=*/true);
ASSERT_TRUE(variable_accessor.AddUniformParameter({"var", int32_t(1)}));
std::string result;
ASSERT_EQ(variable_accessor.Rewrite("var", &result), RewriteStatus::SUCCESS);
EXPECT_EQ(result, "1");
}
TEST(PreprocessorTest, ValueVec) {
VariableAccessor variable_accessor(/*inline_values=*/true);
ASSERT_TRUE(variable_accessor.AddUniformParameter({"var", int2(1, 2)}));
std::string result;
ASSERT_EQ(variable_accessor.Rewrite("var", &result), RewriteStatus::SUCCESS);
EXPECT_EQ(result, "ivec2(1,2)");
}
TEST(PreprocessorTest, Field) {
VariableAccessor variable_accessor(/*inline_values=*/true);
ASSERT_TRUE(
variable_accessor.AddUniformParameter({"var", float2(1.0, 2.1234567)}));
std::string result;
ASSERT_EQ(variable_accessor.Rewrite("var.y", &result),
RewriteStatus::SUCCESS);
EXPECT_EQ(result, "2.123456717f");
}
TEST(PreprocessorTest, FieldFail) {
VariableAccessor variable_accessor(/*inline_values=*/true);
ASSERT_TRUE(variable_accessor.AddUniformParameter({"var", 1.0f}));
ASSERT_TRUE(variable_accessor.AddUniformParameter({"vec", float2(1.0, 1.0)}));
std::string result;
ASSERT_EQ(variable_accessor.Rewrite("var.y", &result), RewriteStatus::ERROR);
EXPECT_EQ(result, "INVALID_ACCESS_BY_FIELD");
result.clear();
ASSERT_EQ(variable_accessor.Rewrite("vec.z", &result), RewriteStatus::ERROR);
EXPECT_EQ(result, "INVALID_ACCESS_BY_FIELD");
}
TEST(PreprocessorTest, Variable) {
VariableAccessor variable_accessor(/*inline_values=*/true);
std::vector<int2> v;
v.push_back(int2(1, 2));
ASSERT_TRUE(variable_accessor.AddUniformParameter({"var", v}));
std::string result;
ASSERT_EQ(variable_accessor.Rewrite("var[i].y", &result),
RewriteStatus::SUCCESS);
ASSERT_EQ(result, "var[i].y");
EXPECT_EQ(variable_accessor.GetConstDeclarations(),
"const ivec2 var[] = ivec2[1](ivec2(1,2));\n");
}
TEST(PreprocessorTest, InlineVariableFail) {
VariableAccessor variable_accessor(/*inline_values=*/true);
ASSERT_TRUE(variable_accessor.AddUniformParameter({"var", 1}));
std::string result;
ASSERT_EQ(variable_accessor.Rewrite("var[i]", &result), RewriteStatus::ERROR);
EXPECT_EQ(result, "INVALID_ACCESS_BY_INDEX");
}
} // namespace
} // namespace gl
} // namespace gpu
} // namespace tflite