Add ktx
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
# Copyright 2017-2020 The Khronos Group Inc.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
function(set_test_properties test_target)
|
||||
# See comments in set_tools_properties() in ../tools/CMakeLists.txt.
|
||||
if(APPLE)
|
||||
set_target_properties(${test_target} PROPERTIES
|
||||
INSTALL_RPATH "@executable_path;@executable_path/../${CMAKE_INSTALL_LIBDIR}"
|
||||
)
|
||||
elseif(LINUX)
|
||||
set_target_properties(${test_target} PROPERTIES
|
||||
INSTALL_RPATH "\$ORIGIN;\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}"
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
if(KTX_FEATURE_LOADTEST_APPS)
|
||||
# Test apps that load and display various KTX textures
|
||||
add_subdirectory(loadtests)
|
||||
endif()
|
||||
|
||||
# gtest based unit-tests
|
||||
if(KTX_FEATURE_TESTS)
|
||||
# Used by texturetests as well as CTS.
|
||||
add_subdirectory(ktxdiff)
|
||||
set(KTX_DIFF_PATH $<TARGET_FILE:ktxdiff>)
|
||||
include(tests.cmake)
|
||||
endif()
|
||||
|
||||
# tools tests
|
||||
if(KTX_FEATURE_TOOLS)
|
||||
include( ktx2check-tests.cmake )
|
||||
include( ktx2ktx2-tests.cmake )
|
||||
include( ktxsc-tests.cmake )
|
||||
include( toktx-tests.cmake )
|
||||
|
||||
# ktx cli tool tests
|
||||
if(KTX_FEATURE_TOOLS_CTS)
|
||||
set(KTX_TOOLS_PATH $<TARGET_FILE:ktxtools>)
|
||||
if(NOT KTX_DIFF_PATH)
|
||||
set(KTX_DIFF_PATH $<TARGET_FILE:ktxdiff>)
|
||||
endif()
|
||||
add_subdirectory(cts/clitests)
|
||||
endif()
|
||||
endif()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
# Copyright 2024 The Khronos Group Inc.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
---
|
||||
# Disable clang-format in this directory
|
||||
DisableFormat: true
|
||||
SortIncludes: false
|
||||
...
|
||||
@@ -0,0 +1,25 @@
|
||||
# Copyright 2017-2020 The Khronos Group Inc.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
add_library( gtest STATIC
|
||||
src/gtest-all.cc
|
||||
src/gtest-internal-inl.h
|
||||
src/gtest_main.cc
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
gtest
|
||||
PUBLIC
|
||||
include
|
||||
.
|
||||
)
|
||||
|
||||
target_compile_features(gtest PUBLIC cxx_std_11)
|
||||
|
||||
if(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" OR ${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
|
||||
set_source_files_properties(
|
||||
src/gtest-all.cc
|
||||
PROPERTIES COMPILE_OPTIONS "-Wno-missing-field-initializers"
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
Why is gtest source included here?
|
||||
==================================
|
||||
|
||||
1. Compiled gtest libraries are incompatible with anything but the MSVS
|
||||
version that compiled them.
|
||||
|
||||
2. The repo includes googlemock and a lot of other code not needed here.
|
||||
|
||||
3. No 2 + the complexities of git submodule and git subtree make depot
|
||||
inclusion unappealing.
|
||||
|
||||
Note: This is bog standard gtest.
|
||||
@@ -0,0 +1,346 @@
|
||||
// Copyright 2005, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// The Google C++ Testing and Mocking Framework (Google Test)
|
||||
//
|
||||
// This header file defines the public API for death tests. It is
|
||||
// #included by gtest.h so a user doesn't need to include this
|
||||
// directly.
|
||||
// GOOGLETEST_CM0001 DO NOT DELETE
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_
|
||||
|
||||
#include "gtest/internal/gtest-death-test-internal.h"
|
||||
|
||||
namespace testing {
|
||||
|
||||
// This flag controls the style of death tests. Valid values are "threadsafe",
|
||||
// meaning that the death test child process will re-execute the test binary
|
||||
// from the start, running only a single death test, or "fast",
|
||||
// meaning that the child process will execute the test logic immediately
|
||||
// after forking.
|
||||
GTEST_DECLARE_string_(death_test_style);
|
||||
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Returns a Boolean value indicating whether the caller is currently
|
||||
// executing in the context of the death test child process. Tools such as
|
||||
// Valgrind heap checkers may need this to modify their behavior in death
|
||||
// tests. IMPORTANT: This is an internal utility. Using it may break the
|
||||
// implementation of death tests. User code MUST NOT use it.
|
||||
GTEST_API_ bool InDeathTestChild();
|
||||
|
||||
} // namespace internal
|
||||
|
||||
// The following macros are useful for writing death tests.
|
||||
|
||||
// Here's what happens when an ASSERT_DEATH* or EXPECT_DEATH* is
|
||||
// executed:
|
||||
//
|
||||
// 1. It generates a warning if there is more than one active
|
||||
// thread. This is because it's safe to fork() or clone() only
|
||||
// when there is a single thread.
|
||||
//
|
||||
// 2. The parent process clone()s a sub-process and runs the death
|
||||
// test in it; the sub-process exits with code 0 at the end of the
|
||||
// death test, if it hasn't exited already.
|
||||
//
|
||||
// 3. The parent process waits for the sub-process to terminate.
|
||||
//
|
||||
// 4. The parent process checks the exit code and error message of
|
||||
// the sub-process.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// ASSERT_DEATH(server.SendMessage(56, "Hello"), "Invalid port number");
|
||||
// for (int i = 0; i < 5; i++) {
|
||||
// EXPECT_DEATH(server.ProcessRequest(i),
|
||||
// "Invalid request .* in ProcessRequest()")
|
||||
// << "Failed to die on request " << i;
|
||||
// }
|
||||
//
|
||||
// ASSERT_EXIT(server.ExitNow(), ::testing::ExitedWithCode(0), "Exiting");
|
||||
//
|
||||
// bool KilledBySIGHUP(int exit_code) {
|
||||
// return WIFSIGNALED(exit_code) && WTERMSIG(exit_code) == SIGHUP;
|
||||
// }
|
||||
//
|
||||
// ASSERT_EXIT(client.HangUpServer(), KilledBySIGHUP, "Hanging up!");
|
||||
//
|
||||
// The final parameter to each of these macros is a matcher applied to any data
|
||||
// the sub-process wrote to stderr. For compatibility with existing tests, a
|
||||
// bare string is interpreted as a regular expression matcher.
|
||||
//
|
||||
// On the regular expressions used in death tests:
|
||||
//
|
||||
// GOOGLETEST_CM0005 DO NOT DELETE
|
||||
// On POSIX-compliant systems (*nix), we use the <regex.h> library,
|
||||
// which uses the POSIX extended regex syntax.
|
||||
//
|
||||
// On other platforms (e.g. Windows or Mac), we only support a simple regex
|
||||
// syntax implemented as part of Google Test. This limited
|
||||
// implementation should be enough most of the time when writing
|
||||
// death tests; though it lacks many features you can find in PCRE
|
||||
// or POSIX extended regex syntax. For example, we don't support
|
||||
// union ("x|y"), grouping ("(xy)"), brackets ("[xy]"), and
|
||||
// repetition count ("x{5,7}"), among others.
|
||||
//
|
||||
// Below is the syntax that we do support. We chose it to be a
|
||||
// subset of both PCRE and POSIX extended regex, so it's easy to
|
||||
// learn wherever you come from. In the following: 'A' denotes a
|
||||
// literal character, period (.), or a single \\ escape sequence;
|
||||
// 'x' and 'y' denote regular expressions; 'm' and 'n' are for
|
||||
// natural numbers.
|
||||
//
|
||||
// c matches any literal character c
|
||||
// \\d matches any decimal digit
|
||||
// \\D matches any character that's not a decimal digit
|
||||
// \\f matches \f
|
||||
// \\n matches \n
|
||||
// \\r matches \r
|
||||
// \\s matches any ASCII whitespace, including \n
|
||||
// \\S matches any character that's not a whitespace
|
||||
// \\t matches \t
|
||||
// \\v matches \v
|
||||
// \\w matches any letter, _, or decimal digit
|
||||
// \\W matches any character that \\w doesn't match
|
||||
// \\c matches any literal character c, which must be a punctuation
|
||||
// . matches any single character except \n
|
||||
// A? matches 0 or 1 occurrences of A
|
||||
// A* matches 0 or many occurrences of A
|
||||
// A+ matches 1 or many occurrences of A
|
||||
// ^ matches the beginning of a string (not that of each line)
|
||||
// $ matches the end of a string (not that of each line)
|
||||
// xy matches x followed by y
|
||||
//
|
||||
// If you accidentally use PCRE or POSIX extended regex features
|
||||
// not implemented by us, you will get a run-time failure. In that
|
||||
// case, please try to rewrite your regular expression within the
|
||||
// above syntax.
|
||||
//
|
||||
// This implementation is *not* meant to be as highly tuned or robust
|
||||
// as a compiled regex library, but should perform well enough for a
|
||||
// death test, which already incurs significant overhead by launching
|
||||
// a child process.
|
||||
//
|
||||
// Known caveats:
|
||||
//
|
||||
// A "threadsafe" style death test obtains the path to the test
|
||||
// program from argv[0] and re-executes it in the sub-process. For
|
||||
// simplicity, the current implementation doesn't search the PATH
|
||||
// when launching the sub-process. This means that the user must
|
||||
// invoke the test program via a path that contains at least one
|
||||
// path separator (e.g. path/to/foo_test and
|
||||
// /absolute/path/to/bar_test are fine, but foo_test is not). This
|
||||
// is rarely a problem as people usually don't put the test binary
|
||||
// directory in PATH.
|
||||
//
|
||||
|
||||
// Asserts that a given `statement` causes the program to exit, with an
|
||||
// integer exit status that satisfies `predicate`, and emitting error output
|
||||
// that matches `matcher`.
|
||||
# define ASSERT_EXIT(statement, predicate, matcher) \
|
||||
GTEST_DEATH_TEST_(statement, predicate, matcher, GTEST_FATAL_FAILURE_)
|
||||
|
||||
// Like `ASSERT_EXIT`, but continues on to successive tests in the
|
||||
// test suite, if any:
|
||||
# define EXPECT_EXIT(statement, predicate, matcher) \
|
||||
GTEST_DEATH_TEST_(statement, predicate, matcher, GTEST_NONFATAL_FAILURE_)
|
||||
|
||||
// Asserts that a given `statement` causes the program to exit, either by
|
||||
// explicitly exiting with a nonzero exit code or being killed by a
|
||||
// signal, and emitting error output that matches `matcher`.
|
||||
# define ASSERT_DEATH(statement, matcher) \
|
||||
ASSERT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, matcher)
|
||||
|
||||
// Like `ASSERT_DEATH`, but continues on to successive tests in the
|
||||
// test suite, if any:
|
||||
# define EXPECT_DEATH(statement, matcher) \
|
||||
EXPECT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, matcher)
|
||||
|
||||
// Two predicate classes that can be used in {ASSERT,EXPECT}_EXIT*:
|
||||
|
||||
// Tests that an exit code describes a normal exit with a given exit code.
|
||||
class GTEST_API_ ExitedWithCode {
|
||||
public:
|
||||
explicit ExitedWithCode(int exit_code);
|
||||
ExitedWithCode(const ExitedWithCode&) = default;
|
||||
void operator=(const ExitedWithCode& other) = delete;
|
||||
bool operator()(int exit_status) const;
|
||||
private:
|
||||
const int exit_code_;
|
||||
};
|
||||
|
||||
# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA
|
||||
// Tests that an exit code describes an exit due to termination by a
|
||||
// given signal.
|
||||
// GOOGLETEST_CM0006 DO NOT DELETE
|
||||
class GTEST_API_ KilledBySignal {
|
||||
public:
|
||||
explicit KilledBySignal(int signum);
|
||||
bool operator()(int exit_status) const;
|
||||
private:
|
||||
const int signum_;
|
||||
};
|
||||
# endif // !GTEST_OS_WINDOWS
|
||||
|
||||
// EXPECT_DEBUG_DEATH asserts that the given statements die in debug mode.
|
||||
// The death testing framework causes this to have interesting semantics,
|
||||
// since the sideeffects of the call are only visible in opt mode, and not
|
||||
// in debug mode.
|
||||
//
|
||||
// In practice, this can be used to test functions that utilize the
|
||||
// LOG(DFATAL) macro using the following style:
|
||||
//
|
||||
// int DieInDebugOr12(int* sideeffect) {
|
||||
// if (sideeffect) {
|
||||
// *sideeffect = 12;
|
||||
// }
|
||||
// LOG(DFATAL) << "death";
|
||||
// return 12;
|
||||
// }
|
||||
//
|
||||
// TEST(TestSuite, TestDieOr12WorksInDgbAndOpt) {
|
||||
// int sideeffect = 0;
|
||||
// // Only asserts in dbg.
|
||||
// EXPECT_DEBUG_DEATH(DieInDebugOr12(&sideeffect), "death");
|
||||
//
|
||||
// #ifdef NDEBUG
|
||||
// // opt-mode has sideeffect visible.
|
||||
// EXPECT_EQ(12, sideeffect);
|
||||
// #else
|
||||
// // dbg-mode no visible sideeffect.
|
||||
// EXPECT_EQ(0, sideeffect);
|
||||
// #endif
|
||||
// }
|
||||
//
|
||||
// This will assert that DieInDebugReturn12InOpt() crashes in debug
|
||||
// mode, usually due to a DCHECK or LOG(DFATAL), but returns the
|
||||
// appropriate fallback value (12 in this case) in opt mode. If you
|
||||
// need to test that a function has appropriate side-effects in opt
|
||||
// mode, include assertions against the side-effects. A general
|
||||
// pattern for this is:
|
||||
//
|
||||
// EXPECT_DEBUG_DEATH({
|
||||
// // Side-effects here will have an effect after this statement in
|
||||
// // opt mode, but none in debug mode.
|
||||
// EXPECT_EQ(12, DieInDebugOr12(&sideeffect));
|
||||
// }, "death");
|
||||
//
|
||||
# ifdef NDEBUG
|
||||
|
||||
# define EXPECT_DEBUG_DEATH(statement, regex) \
|
||||
GTEST_EXECUTE_STATEMENT_(statement, regex)
|
||||
|
||||
# define ASSERT_DEBUG_DEATH(statement, regex) \
|
||||
GTEST_EXECUTE_STATEMENT_(statement, regex)
|
||||
|
||||
# else
|
||||
|
||||
# define EXPECT_DEBUG_DEATH(statement, regex) \
|
||||
EXPECT_DEATH(statement, regex)
|
||||
|
||||
# define ASSERT_DEBUG_DEATH(statement, regex) \
|
||||
ASSERT_DEATH(statement, regex)
|
||||
|
||||
# endif // NDEBUG for EXPECT_DEBUG_DEATH
|
||||
#endif // GTEST_HAS_DEATH_TEST
|
||||
|
||||
// This macro is used for implementing macros such as
|
||||
// EXPECT_DEATH_IF_SUPPORTED and ASSERT_DEATH_IF_SUPPORTED on systems where
|
||||
// death tests are not supported. Those macros must compile on such systems
|
||||
// if and only if EXPECT_DEATH and ASSERT_DEATH compile with the same parameters
|
||||
// on systems that support death tests. This allows one to write such a macro on
|
||||
// a system that does not support death tests and be sure that it will compile
|
||||
// on a death-test supporting system. It is exposed publicly so that systems
|
||||
// that have death-tests with stricter requirements than GTEST_HAS_DEATH_TEST
|
||||
// can write their own equivalent of EXPECT_DEATH_IF_SUPPORTED and
|
||||
// ASSERT_DEATH_IF_SUPPORTED.
|
||||
//
|
||||
// Parameters:
|
||||
// statement - A statement that a macro such as EXPECT_DEATH would test
|
||||
// for program termination. This macro has to make sure this
|
||||
// statement is compiled but not executed, to ensure that
|
||||
// EXPECT_DEATH_IF_SUPPORTED compiles with a certain
|
||||
// parameter if and only if EXPECT_DEATH compiles with it.
|
||||
// regex - A regex that a macro such as EXPECT_DEATH would use to test
|
||||
// the output of statement. This parameter has to be
|
||||
// compiled but not evaluated by this macro, to ensure that
|
||||
// this macro only accepts expressions that a macro such as
|
||||
// EXPECT_DEATH would accept.
|
||||
// terminator - Must be an empty statement for EXPECT_DEATH_IF_SUPPORTED
|
||||
// and a return statement for ASSERT_DEATH_IF_SUPPORTED.
|
||||
// This ensures that ASSERT_DEATH_IF_SUPPORTED will not
|
||||
// compile inside functions where ASSERT_DEATH doesn't
|
||||
// compile.
|
||||
//
|
||||
// The branch that has an always false condition is used to ensure that
|
||||
// statement and regex are compiled (and thus syntactically correct) but
|
||||
// never executed. The unreachable code macro protects the terminator
|
||||
// statement from generating an 'unreachable code' warning in case
|
||||
// statement unconditionally returns or throws. The Message constructor at
|
||||
// the end allows the syntax of streaming additional messages into the
|
||||
// macro, for compilational compatibility with EXPECT_DEATH/ASSERT_DEATH.
|
||||
# define GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, terminator) \
|
||||
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
|
||||
if (::testing::internal::AlwaysTrue()) { \
|
||||
GTEST_LOG_(WARNING) \
|
||||
<< "Death tests are not supported on this platform.\n" \
|
||||
<< "Statement '" #statement "' cannot be verified."; \
|
||||
} else if (::testing::internal::AlwaysFalse()) { \
|
||||
::testing::internal::RE::PartialMatch(".*", (regex)); \
|
||||
GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
|
||||
terminator; \
|
||||
} else \
|
||||
::testing::Message()
|
||||
|
||||
// EXPECT_DEATH_IF_SUPPORTED(statement, regex) and
|
||||
// ASSERT_DEATH_IF_SUPPORTED(statement, regex) expand to real death tests if
|
||||
// death tests are supported; otherwise they just issue a warning. This is
|
||||
// useful when you are combining death test assertions with normal test
|
||||
// assertions in one test.
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
# define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \
|
||||
EXPECT_DEATH(statement, regex)
|
||||
# define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \
|
||||
ASSERT_DEATH(statement, regex)
|
||||
#else
|
||||
# define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \
|
||||
GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, )
|
||||
# define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \
|
||||
GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, return)
|
||||
#endif
|
||||
|
||||
} // namespace testing
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_
|
||||
@@ -0,0 +1,930 @@
|
||||
// Copyright 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// The Google C++ Testing and Mocking Framework (Google Test)
|
||||
//
|
||||
// This file implements just enough of the matcher interface to allow
|
||||
// EXPECT_DEATH and friends to accept a matcher argument.
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_MATCHERS_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_MATCHERS_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "gtest/gtest-printers.h"
|
||||
#include "gtest/internal/gtest-internal.h"
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
|
||||
// MSVC warning C5046 is new as of VS2017 version 15.8.
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1915
|
||||
#define GTEST_MAYBE_5046_ 5046
|
||||
#else
|
||||
#define GTEST_MAYBE_5046_
|
||||
#endif
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_PUSH_(
|
||||
4251 GTEST_MAYBE_5046_ /* class A needs to have dll-interface to be used by
|
||||
clients of class B */
|
||||
/* Symbol involving type with internal linkage not defined */)
|
||||
|
||||
namespace testing {
|
||||
|
||||
// To implement a matcher Foo for type T, define:
|
||||
// 1. a class FooMatcherMatcher that implements the matcher interface:
|
||||
// using is_gtest_matcher = void;
|
||||
// bool MatchAndExplain(const T&, std::ostream*);
|
||||
// (MatchResultListener* can also be used instead of std::ostream*)
|
||||
// void DescribeTo(std::ostream*);
|
||||
// void DescribeNegationTo(std::ostream*);
|
||||
//
|
||||
// 2. a factory function that creates a Matcher<T> object from a
|
||||
// FooMatcherMatcher.
|
||||
|
||||
class MatchResultListener {
|
||||
public:
|
||||
// Creates a listener object with the given underlying ostream. The
|
||||
// listener does not own the ostream, and does not dereference it
|
||||
// in the constructor or destructor.
|
||||
explicit MatchResultListener(::std::ostream* os) : stream_(os) {}
|
||||
virtual ~MatchResultListener() = 0; // Makes this class abstract.
|
||||
|
||||
// Streams x to the underlying ostream; does nothing if the ostream
|
||||
// is NULL.
|
||||
template <typename T>
|
||||
MatchResultListener& operator<<(const T& x) {
|
||||
if (stream_ != nullptr) *stream_ << x;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Returns the underlying ostream.
|
||||
::std::ostream* stream() { return stream_; }
|
||||
|
||||
// Returns true if and only if the listener is interested in an explanation
|
||||
// of the match result. A matcher's MatchAndExplain() method can use
|
||||
// this information to avoid generating the explanation when no one
|
||||
// intends to hear it.
|
||||
bool IsInterested() const { return stream_ != nullptr; }
|
||||
|
||||
private:
|
||||
::std::ostream* const stream_;
|
||||
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(MatchResultListener);
|
||||
};
|
||||
|
||||
inline MatchResultListener::~MatchResultListener() {
|
||||
}
|
||||
|
||||
// An instance of a subclass of this knows how to describe itself as a
|
||||
// matcher.
|
||||
class GTEST_API_ MatcherDescriberInterface {
|
||||
public:
|
||||
virtual ~MatcherDescriberInterface() {}
|
||||
|
||||
// Describes this matcher to an ostream. The function should print
|
||||
// a verb phrase that describes the property a value matching this
|
||||
// matcher should have. The subject of the verb phrase is the value
|
||||
// being matched. For example, the DescribeTo() method of the Gt(7)
|
||||
// matcher prints "is greater than 7".
|
||||
virtual void DescribeTo(::std::ostream* os) const = 0;
|
||||
|
||||
// Describes the negation of this matcher to an ostream. For
|
||||
// example, if the description of this matcher is "is greater than
|
||||
// 7", the negated description could be "is not greater than 7".
|
||||
// You are not required to override this when implementing
|
||||
// MatcherInterface, but it is highly advised so that your matcher
|
||||
// can produce good error messages.
|
||||
virtual void DescribeNegationTo(::std::ostream* os) const {
|
||||
*os << "not (";
|
||||
DescribeTo(os);
|
||||
*os << ")";
|
||||
}
|
||||
};
|
||||
|
||||
// The implementation of a matcher.
|
||||
template <typename T>
|
||||
class MatcherInterface : public MatcherDescriberInterface {
|
||||
public:
|
||||
// Returns true if and only if the matcher matches x; also explains the
|
||||
// match result to 'listener' if necessary (see the next paragraph), in
|
||||
// the form of a non-restrictive relative clause ("which ...",
|
||||
// "whose ...", etc) that describes x. For example, the
|
||||
// MatchAndExplain() method of the Pointee(...) matcher should
|
||||
// generate an explanation like "which points to ...".
|
||||
//
|
||||
// Implementations of MatchAndExplain() should add an explanation of
|
||||
// the match result *if and only if* they can provide additional
|
||||
// information that's not already present (or not obvious) in the
|
||||
// print-out of x and the matcher's description. Whether the match
|
||||
// succeeds is not a factor in deciding whether an explanation is
|
||||
// needed, as sometimes the caller needs to print a failure message
|
||||
// when the match succeeds (e.g. when the matcher is used inside
|
||||
// Not()).
|
||||
//
|
||||
// For example, a "has at least 10 elements" matcher should explain
|
||||
// what the actual element count is, regardless of the match result,
|
||||
// as it is useful information to the reader; on the other hand, an
|
||||
// "is empty" matcher probably only needs to explain what the actual
|
||||
// size is when the match fails, as it's redundant to say that the
|
||||
// size is 0 when the value is already known to be empty.
|
||||
//
|
||||
// You should override this method when defining a new matcher.
|
||||
//
|
||||
// It's the responsibility of the caller (Google Test) to guarantee
|
||||
// that 'listener' is not NULL. This helps to simplify a matcher's
|
||||
// implementation when it doesn't care about the performance, as it
|
||||
// can talk to 'listener' without checking its validity first.
|
||||
// However, in order to implement dummy listeners efficiently,
|
||||
// listener->stream() may be NULL.
|
||||
virtual bool MatchAndExplain(T x, MatchResultListener* listener) const = 0;
|
||||
|
||||
// Inherits these methods from MatcherDescriberInterface:
|
||||
// virtual void DescribeTo(::std::ostream* os) const = 0;
|
||||
// virtual void DescribeNegationTo(::std::ostream* os) const;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
struct AnyEq {
|
||||
template <typename A, typename B>
|
||||
bool operator()(const A& a, const B& b) const { return a == b; }
|
||||
};
|
||||
struct AnyNe {
|
||||
template <typename A, typename B>
|
||||
bool operator()(const A& a, const B& b) const { return a != b; }
|
||||
};
|
||||
struct AnyLt {
|
||||
template <typename A, typename B>
|
||||
bool operator()(const A& a, const B& b) const { return a < b; }
|
||||
};
|
||||
struct AnyGt {
|
||||
template <typename A, typename B>
|
||||
bool operator()(const A& a, const B& b) const { return a > b; }
|
||||
};
|
||||
struct AnyLe {
|
||||
template <typename A, typename B>
|
||||
bool operator()(const A& a, const B& b) const { return a <= b; }
|
||||
};
|
||||
struct AnyGe {
|
||||
template <typename A, typename B>
|
||||
bool operator()(const A& a, const B& b) const { return a >= b; }
|
||||
};
|
||||
|
||||
// A match result listener that ignores the explanation.
|
||||
class DummyMatchResultListener : public MatchResultListener {
|
||||
public:
|
||||
DummyMatchResultListener() : MatchResultListener(nullptr) {}
|
||||
|
||||
private:
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(DummyMatchResultListener);
|
||||
};
|
||||
|
||||
// A match result listener that forwards the explanation to a given
|
||||
// ostream. The difference between this and MatchResultListener is
|
||||
// that the former is concrete.
|
||||
class StreamMatchResultListener : public MatchResultListener {
|
||||
public:
|
||||
explicit StreamMatchResultListener(::std::ostream* os)
|
||||
: MatchResultListener(os) {}
|
||||
|
||||
private:
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(StreamMatchResultListener);
|
||||
};
|
||||
|
||||
struct SharedPayloadBase {
|
||||
std::atomic<int> ref{1};
|
||||
void Ref() { ref.fetch_add(1, std::memory_order_relaxed); }
|
||||
bool Unref() { return ref.fetch_sub(1, std::memory_order_acq_rel) == 1; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct SharedPayload : SharedPayloadBase {
|
||||
explicit SharedPayload(const T& v) : value(v) {}
|
||||
explicit SharedPayload(T&& v) : value(std::move(v)) {}
|
||||
|
||||
static void Destroy(SharedPayloadBase* shared) {
|
||||
delete static_cast<SharedPayload*>(shared);
|
||||
}
|
||||
|
||||
T value;
|
||||
};
|
||||
|
||||
// An internal class for implementing Matcher<T>, which will derive
|
||||
// from it. We put functionalities common to all Matcher<T>
|
||||
// specializations here to avoid code duplication.
|
||||
template <typename T>
|
||||
class MatcherBase : private MatcherDescriberInterface {
|
||||
public:
|
||||
// Returns true if and only if the matcher matches x; also explains the
|
||||
// match result to 'listener'.
|
||||
bool MatchAndExplain(const T& x, MatchResultListener* listener) const {
|
||||
GTEST_CHECK_(vtable_ != nullptr);
|
||||
return vtable_->match_and_explain(*this, x, listener);
|
||||
}
|
||||
|
||||
// Returns true if and only if this matcher matches x.
|
||||
bool Matches(const T& x) const {
|
||||
DummyMatchResultListener dummy;
|
||||
return MatchAndExplain(x, &dummy);
|
||||
}
|
||||
|
||||
// Describes this matcher to an ostream.
|
||||
void DescribeTo(::std::ostream* os) const final {
|
||||
GTEST_CHECK_(vtable_ != nullptr);
|
||||
vtable_->describe(*this, os, false);
|
||||
}
|
||||
|
||||
// Describes the negation of this matcher to an ostream.
|
||||
void DescribeNegationTo(::std::ostream* os) const final {
|
||||
GTEST_CHECK_(vtable_ != nullptr);
|
||||
vtable_->describe(*this, os, true);
|
||||
}
|
||||
|
||||
// Explains why x matches, or doesn't match, the matcher.
|
||||
void ExplainMatchResultTo(const T& x, ::std::ostream* os) const {
|
||||
StreamMatchResultListener listener(os);
|
||||
MatchAndExplain(x, &listener);
|
||||
}
|
||||
|
||||
// Returns the describer for this matcher object; retains ownership
|
||||
// of the describer, which is only guaranteed to be alive when
|
||||
// this matcher object is alive.
|
||||
const MatcherDescriberInterface* GetDescriber() const {
|
||||
if (vtable_ == nullptr) return nullptr;
|
||||
return vtable_->get_describer(*this);
|
||||
}
|
||||
|
||||
protected:
|
||||
MatcherBase() : vtable_(nullptr) {}
|
||||
|
||||
// Constructs a matcher from its implementation.
|
||||
template <typename U>
|
||||
explicit MatcherBase(const MatcherInterface<U>* impl) {
|
||||
Init(impl);
|
||||
}
|
||||
|
||||
template <typename M, typename = typename std::remove_reference<
|
||||
M>::type::is_gtest_matcher>
|
||||
MatcherBase(M&& m) { // NOLINT
|
||||
Init(std::forward<M>(m));
|
||||
}
|
||||
|
||||
MatcherBase(const MatcherBase& other)
|
||||
: vtable_(other.vtable_), buffer_(other.buffer_) {
|
||||
if (IsShared()) buffer_.shared->Ref();
|
||||
}
|
||||
|
||||
MatcherBase& operator=(const MatcherBase& other) {
|
||||
if (this == &other) return *this;
|
||||
Destroy();
|
||||
vtable_ = other.vtable_;
|
||||
buffer_ = other.buffer_;
|
||||
if (IsShared()) buffer_.shared->Ref();
|
||||
return *this;
|
||||
}
|
||||
|
||||
MatcherBase(MatcherBase&& other)
|
||||
: vtable_(other.vtable_), buffer_(other.buffer_) {
|
||||
other.vtable_ = nullptr;
|
||||
}
|
||||
|
||||
MatcherBase& operator=(MatcherBase&& other) {
|
||||
if (this == &other) return *this;
|
||||
Destroy();
|
||||
vtable_ = other.vtable_;
|
||||
buffer_ = other.buffer_;
|
||||
other.vtable_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~MatcherBase() override { Destroy(); }
|
||||
|
||||
private:
|
||||
struct VTable {
|
||||
bool (*match_and_explain)(const MatcherBase&, const T&,
|
||||
MatchResultListener*);
|
||||
void (*describe)(const MatcherBase&, std::ostream*, bool negation);
|
||||
// Returns the captured object if it implements the interface, otherwise
|
||||
// returns the MatcherBase itself.
|
||||
const MatcherDescriberInterface* (*get_describer)(const MatcherBase&);
|
||||
// Called on shared instances when the reference count reaches 0.
|
||||
void (*shared_destroy)(SharedPayloadBase*);
|
||||
};
|
||||
|
||||
bool IsShared() const {
|
||||
return vtable_ != nullptr && vtable_->shared_destroy != nullptr;
|
||||
}
|
||||
|
||||
// If the implementation uses a listener, call that.
|
||||
template <typename P>
|
||||
static auto MatchAndExplainImpl(const MatcherBase& m, const T& value,
|
||||
MatchResultListener* listener)
|
||||
-> decltype(P::Get(m).MatchAndExplain(value, listener->stream())) {
|
||||
return P::Get(m).MatchAndExplain(value, listener->stream());
|
||||
}
|
||||
|
||||
template <typename P>
|
||||
static auto MatchAndExplainImpl(const MatcherBase& m, const T& value,
|
||||
MatchResultListener* listener)
|
||||
-> decltype(P::Get(m).MatchAndExplain(value, listener)) {
|
||||
return P::Get(m).MatchAndExplain(value, listener);
|
||||
}
|
||||
|
||||
template <typename P>
|
||||
static void DescribeImpl(const MatcherBase& m, std::ostream* os,
|
||||
bool negation) {
|
||||
if (negation) {
|
||||
P::Get(m).DescribeNegationTo(os);
|
||||
} else {
|
||||
P::Get(m).DescribeTo(os);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename P>
|
||||
static const MatcherDescriberInterface* GetDescriberImpl(
|
||||
const MatcherBase& m) {
|
||||
// If the impl is a MatcherDescriberInterface, then return it.
|
||||
// Otherwise use MatcherBase itself.
|
||||
// This allows us to implement the GetDescriber() function without support
|
||||
// from the impl, but some users really want to get their impl back when
|
||||
// they call GetDescriber().
|
||||
// We use std::get on a tuple as a workaround of not having `if constexpr`.
|
||||
return std::get<(
|
||||
std::is_convertible<decltype(&P::Get(m)),
|
||||
const MatcherDescriberInterface*>::value
|
||||
? 1
|
||||
: 0)>(std::make_tuple(&m, &P::Get(m)));
|
||||
}
|
||||
|
||||
template <typename P>
|
||||
const VTable* GetVTable() {
|
||||
static constexpr VTable kVTable = {&MatchAndExplainImpl<P>,
|
||||
&DescribeImpl<P>, &GetDescriberImpl<P>,
|
||||
P::shared_destroy};
|
||||
return &kVTable;
|
||||
}
|
||||
|
||||
union Buffer {
|
||||
// Add some types to give Buffer some common alignment/size use cases.
|
||||
void* ptr;
|
||||
double d;
|
||||
int64_t i;
|
||||
// And add one for the out-of-line cases.
|
||||
SharedPayloadBase* shared;
|
||||
};
|
||||
|
||||
void Destroy() {
|
||||
if (IsShared() && buffer_.shared->Unref()) {
|
||||
vtable_->shared_destroy(buffer_.shared);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename M>
|
||||
static constexpr bool IsInlined() {
|
||||
return sizeof(M) <= sizeof(Buffer) && alignof(M) <= alignof(Buffer) &&
|
||||
std::is_trivially_copy_constructible<M>::value &&
|
||||
std::is_trivially_destructible<M>::value;
|
||||
}
|
||||
|
||||
template <typename M, bool = MatcherBase::IsInlined<M>()>
|
||||
struct ValuePolicy {
|
||||
static const M& Get(const MatcherBase& m) {
|
||||
// When inlined along with Init, need to be explicit to avoid violating
|
||||
// strict aliasing rules.
|
||||
const M *ptr = static_cast<const M*>(
|
||||
static_cast<const void*>(&m.buffer_));
|
||||
return *ptr;
|
||||
}
|
||||
static void Init(MatcherBase& m, M impl) {
|
||||
::new (static_cast<void*>(&m.buffer_)) M(impl);
|
||||
}
|
||||
static constexpr auto shared_destroy = nullptr;
|
||||
};
|
||||
|
||||
template <typename M>
|
||||
struct ValuePolicy<M, false> {
|
||||
using Shared = SharedPayload<M>;
|
||||
static const M& Get(const MatcherBase& m) {
|
||||
return static_cast<Shared*>(m.buffer_.shared)->value;
|
||||
}
|
||||
template <typename Arg>
|
||||
static void Init(MatcherBase& m, Arg&& arg) {
|
||||
m.buffer_.shared = new Shared(std::forward<Arg>(arg));
|
||||
}
|
||||
static constexpr auto shared_destroy = &Shared::Destroy;
|
||||
};
|
||||
|
||||
template <typename U, bool B>
|
||||
struct ValuePolicy<const MatcherInterface<U>*, B> {
|
||||
using M = const MatcherInterface<U>;
|
||||
using Shared = SharedPayload<std::unique_ptr<M>>;
|
||||
static const M& Get(const MatcherBase& m) {
|
||||
return *static_cast<Shared*>(m.buffer_.shared)->value;
|
||||
}
|
||||
static void Init(MatcherBase& m, M* impl) {
|
||||
m.buffer_.shared = new Shared(std::unique_ptr<M>(impl));
|
||||
}
|
||||
|
||||
static constexpr auto shared_destroy = &Shared::Destroy;
|
||||
};
|
||||
|
||||
template <typename M>
|
||||
void Init(M&& m) {
|
||||
using MM = typename std::decay<M>::type;
|
||||
using Policy = ValuePolicy<MM>;
|
||||
vtable_ = GetVTable<Policy>();
|
||||
Policy::Init(*this, std::forward<M>(m));
|
||||
}
|
||||
|
||||
const VTable* vtable_;
|
||||
Buffer buffer_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
// A Matcher<T> is a copyable and IMMUTABLE (except by assignment)
|
||||
// object that can check whether a value of type T matches. The
|
||||
// implementation of Matcher<T> is just a std::shared_ptr to const
|
||||
// MatcherInterface<T>. Don't inherit from Matcher!
|
||||
template <typename T>
|
||||
class Matcher : public internal::MatcherBase<T> {
|
||||
public:
|
||||
// Constructs a null matcher. Needed for storing Matcher objects in STL
|
||||
// containers. A default-constructed matcher is not yet initialized. You
|
||||
// cannot use it until a valid value has been assigned to it.
|
||||
explicit Matcher() {} // NOLINT
|
||||
|
||||
// Constructs a matcher from its implementation.
|
||||
explicit Matcher(const MatcherInterface<const T&>* impl)
|
||||
: internal::MatcherBase<T>(impl) {}
|
||||
|
||||
template <typename U>
|
||||
explicit Matcher(
|
||||
const MatcherInterface<U>* impl,
|
||||
typename std::enable_if<!std::is_same<U, const U&>::value>::type* =
|
||||
nullptr)
|
||||
: internal::MatcherBase<T>(impl) {}
|
||||
|
||||
template <typename M, typename = typename std::remove_reference<
|
||||
M>::type::is_gtest_matcher>
|
||||
Matcher(M&& m) : internal::MatcherBase<T>(std::forward<M>(m)) {} // NOLINT
|
||||
|
||||
// Implicit constructor here allows people to write
|
||||
// EXPECT_CALL(foo, Bar(5)) instead of EXPECT_CALL(foo, Bar(Eq(5))) sometimes
|
||||
Matcher(T value); // NOLINT
|
||||
};
|
||||
|
||||
// The following two specializations allow the user to write str
|
||||
// instead of Eq(str) and "foo" instead of Eq("foo") when a std::string
|
||||
// matcher is expected.
|
||||
template <>
|
||||
class GTEST_API_ Matcher<const std::string&>
|
||||
: public internal::MatcherBase<const std::string&> {
|
||||
public:
|
||||
Matcher() {}
|
||||
|
||||
explicit Matcher(const MatcherInterface<const std::string&>* impl)
|
||||
: internal::MatcherBase<const std::string&>(impl) {}
|
||||
|
||||
template <typename M, typename = typename std::remove_reference<
|
||||
M>::type::is_gtest_matcher>
|
||||
Matcher(M&& m) // NOLINT
|
||||
: internal::MatcherBase<const std::string&>(std::forward<M>(m)) {}
|
||||
|
||||
// Allows the user to write str instead of Eq(str) sometimes, where
|
||||
// str is a std::string object.
|
||||
Matcher(const std::string& s); // NOLINT
|
||||
|
||||
// Allows the user to write "foo" instead of Eq("foo") sometimes.
|
||||
Matcher(const char* s); // NOLINT
|
||||
};
|
||||
|
||||
template <>
|
||||
class GTEST_API_ Matcher<std::string>
|
||||
: public internal::MatcherBase<std::string> {
|
||||
public:
|
||||
Matcher() {}
|
||||
|
||||
explicit Matcher(const MatcherInterface<const std::string&>* impl)
|
||||
: internal::MatcherBase<std::string>(impl) {}
|
||||
explicit Matcher(const MatcherInterface<std::string>* impl)
|
||||
: internal::MatcherBase<std::string>(impl) {}
|
||||
|
||||
template <typename M, typename = typename std::remove_reference<
|
||||
M>::type::is_gtest_matcher>
|
||||
Matcher(M&& m) // NOLINT
|
||||
: internal::MatcherBase<std::string>(std::forward<M>(m)) {}
|
||||
|
||||
// Allows the user to write str instead of Eq(str) sometimes, where
|
||||
// str is a string object.
|
||||
Matcher(const std::string& s); // NOLINT
|
||||
|
||||
// Allows the user to write "foo" instead of Eq("foo") sometimes.
|
||||
Matcher(const char* s); // NOLINT
|
||||
};
|
||||
|
||||
#if GTEST_INTERNAL_HAS_STRING_VIEW
|
||||
// The following two specializations allow the user to write str
|
||||
// instead of Eq(str) and "foo" instead of Eq("foo") when a absl::string_view
|
||||
// matcher is expected.
|
||||
template <>
|
||||
class GTEST_API_ Matcher<const internal::StringView&>
|
||||
: public internal::MatcherBase<const internal::StringView&> {
|
||||
public:
|
||||
Matcher() {}
|
||||
|
||||
explicit Matcher(const MatcherInterface<const internal::StringView&>* impl)
|
||||
: internal::MatcherBase<const internal::StringView&>(impl) {}
|
||||
|
||||
template <typename M, typename = typename std::remove_reference<
|
||||
M>::type::is_gtest_matcher>
|
||||
Matcher(M&& m) // NOLINT
|
||||
: internal::MatcherBase<const internal::StringView&>(std::forward<M>(m)) {
|
||||
}
|
||||
|
||||
// Allows the user to write str instead of Eq(str) sometimes, where
|
||||
// str is a std::string object.
|
||||
Matcher(const std::string& s); // NOLINT
|
||||
|
||||
// Allows the user to write "foo" instead of Eq("foo") sometimes.
|
||||
Matcher(const char* s); // NOLINT
|
||||
|
||||
// Allows the user to pass absl::string_views or std::string_views directly.
|
||||
Matcher(internal::StringView s); // NOLINT
|
||||
};
|
||||
|
||||
template <>
|
||||
class GTEST_API_ Matcher<internal::StringView>
|
||||
: public internal::MatcherBase<internal::StringView> {
|
||||
public:
|
||||
Matcher() {}
|
||||
|
||||
explicit Matcher(const MatcherInterface<const internal::StringView&>* impl)
|
||||
: internal::MatcherBase<internal::StringView>(impl) {}
|
||||
explicit Matcher(const MatcherInterface<internal::StringView>* impl)
|
||||
: internal::MatcherBase<internal::StringView>(impl) {}
|
||||
|
||||
template <typename M, typename = typename std::remove_reference<
|
||||
M>::type::is_gtest_matcher>
|
||||
Matcher(M&& m) // NOLINT
|
||||
: internal::MatcherBase<internal::StringView>(std::forward<M>(m)) {}
|
||||
|
||||
// Allows the user to write str instead of Eq(str) sometimes, where
|
||||
// str is a std::string object.
|
||||
Matcher(const std::string& s); // NOLINT
|
||||
|
||||
// Allows the user to write "foo" instead of Eq("foo") sometimes.
|
||||
Matcher(const char* s); // NOLINT
|
||||
|
||||
// Allows the user to pass absl::string_views or std::string_views directly.
|
||||
Matcher(internal::StringView s); // NOLINT
|
||||
};
|
||||
#endif // GTEST_INTERNAL_HAS_STRING_VIEW
|
||||
|
||||
// Prints a matcher in a human-readable format.
|
||||
template <typename T>
|
||||
std::ostream& operator<<(std::ostream& os, const Matcher<T>& matcher) {
|
||||
matcher.DescribeTo(&os);
|
||||
return os;
|
||||
}
|
||||
|
||||
// The PolymorphicMatcher class template makes it easy to implement a
|
||||
// polymorphic matcher (i.e. a matcher that can match values of more
|
||||
// than one type, e.g. Eq(n) and NotNull()).
|
||||
//
|
||||
// To define a polymorphic matcher, a user should provide an Impl
|
||||
// class that has a DescribeTo() method and a DescribeNegationTo()
|
||||
// method, and define a member function (or member function template)
|
||||
//
|
||||
// bool MatchAndExplain(const Value& value,
|
||||
// MatchResultListener* listener) const;
|
||||
//
|
||||
// See the definition of NotNull() for a complete example.
|
||||
template <class Impl>
|
||||
class PolymorphicMatcher {
|
||||
public:
|
||||
explicit PolymorphicMatcher(const Impl& an_impl) : impl_(an_impl) {}
|
||||
|
||||
// Returns a mutable reference to the underlying matcher
|
||||
// implementation object.
|
||||
Impl& mutable_impl() { return impl_; }
|
||||
|
||||
// Returns an immutable reference to the underlying matcher
|
||||
// implementation object.
|
||||
const Impl& impl() const { return impl_; }
|
||||
|
||||
template <typename T>
|
||||
operator Matcher<T>() const {
|
||||
return Matcher<T>(new MonomorphicImpl<const T&>(impl_));
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
class MonomorphicImpl : public MatcherInterface<T> {
|
||||
public:
|
||||
explicit MonomorphicImpl(const Impl& impl) : impl_(impl) {}
|
||||
|
||||
void DescribeTo(::std::ostream* os) const override { impl_.DescribeTo(os); }
|
||||
|
||||
void DescribeNegationTo(::std::ostream* os) const override {
|
||||
impl_.DescribeNegationTo(os);
|
||||
}
|
||||
|
||||
bool MatchAndExplain(T x, MatchResultListener* listener) const override {
|
||||
return impl_.MatchAndExplain(x, listener);
|
||||
}
|
||||
|
||||
private:
|
||||
const Impl impl_;
|
||||
};
|
||||
|
||||
Impl impl_;
|
||||
};
|
||||
|
||||
// Creates a matcher from its implementation.
|
||||
// DEPRECATED: Especially in the generic code, prefer:
|
||||
// Matcher<T>(new MyMatcherImpl<const T&>(...));
|
||||
//
|
||||
// MakeMatcher may create a Matcher that accepts its argument by value, which
|
||||
// leads to unnecessary copies & lack of support for non-copyable types.
|
||||
template <typename T>
|
||||
inline Matcher<T> MakeMatcher(const MatcherInterface<T>* impl) {
|
||||
return Matcher<T>(impl);
|
||||
}
|
||||
|
||||
// Creates a polymorphic matcher from its implementation. This is
|
||||
// easier to use than the PolymorphicMatcher<Impl> constructor as it
|
||||
// doesn't require you to explicitly write the template argument, e.g.
|
||||
//
|
||||
// MakePolymorphicMatcher(foo);
|
||||
// vs
|
||||
// PolymorphicMatcher<TypeOfFoo>(foo);
|
||||
template <class Impl>
|
||||
inline PolymorphicMatcher<Impl> MakePolymorphicMatcher(const Impl& impl) {
|
||||
return PolymorphicMatcher<Impl>(impl);
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
// Implements a matcher that compares a given value with a
|
||||
// pre-supplied value using one of the ==, <=, <, etc, operators. The
|
||||
// two values being compared don't have to have the same type.
|
||||
//
|
||||
// The matcher defined here is polymorphic (for example, Eq(5) can be
|
||||
// used to match an int, a short, a double, etc). Therefore we use
|
||||
// a template type conversion operator in the implementation.
|
||||
//
|
||||
// The following template definition assumes that the Rhs parameter is
|
||||
// a "bare" type (i.e. neither 'const T' nor 'T&').
|
||||
template <typename D, typename Rhs, typename Op>
|
||||
class ComparisonBase {
|
||||
public:
|
||||
explicit ComparisonBase(const Rhs& rhs) : rhs_(rhs) {}
|
||||
|
||||
using is_gtest_matcher = void;
|
||||
|
||||
template <typename Lhs>
|
||||
bool MatchAndExplain(const Lhs& lhs, std::ostream*) const {
|
||||
return Op()(lhs, Unwrap(rhs_));
|
||||
}
|
||||
void DescribeTo(std::ostream* os) const {
|
||||
*os << D::Desc() << " ";
|
||||
UniversalPrint(Unwrap(rhs_), os);
|
||||
}
|
||||
void DescribeNegationTo(std::ostream* os) const {
|
||||
*os << D::NegatedDesc() << " ";
|
||||
UniversalPrint(Unwrap(rhs_), os);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
static const T& Unwrap(const T& v) {
|
||||
return v;
|
||||
}
|
||||
template <typename T>
|
||||
static const T& Unwrap(std::reference_wrapper<T> v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
Rhs rhs_;
|
||||
};
|
||||
|
||||
template <typename Rhs>
|
||||
class EqMatcher : public ComparisonBase<EqMatcher<Rhs>, Rhs, AnyEq> {
|
||||
public:
|
||||
explicit EqMatcher(const Rhs& rhs)
|
||||
: ComparisonBase<EqMatcher<Rhs>, Rhs, AnyEq>(rhs) { }
|
||||
static const char* Desc() { return "is equal to"; }
|
||||
static const char* NegatedDesc() { return "isn't equal to"; }
|
||||
};
|
||||
template <typename Rhs>
|
||||
class NeMatcher : public ComparisonBase<NeMatcher<Rhs>, Rhs, AnyNe> {
|
||||
public:
|
||||
explicit NeMatcher(const Rhs& rhs)
|
||||
: ComparisonBase<NeMatcher<Rhs>, Rhs, AnyNe>(rhs) { }
|
||||
static const char* Desc() { return "isn't equal to"; }
|
||||
static const char* NegatedDesc() { return "is equal to"; }
|
||||
};
|
||||
template <typename Rhs>
|
||||
class LtMatcher : public ComparisonBase<LtMatcher<Rhs>, Rhs, AnyLt> {
|
||||
public:
|
||||
explicit LtMatcher(const Rhs& rhs)
|
||||
: ComparisonBase<LtMatcher<Rhs>, Rhs, AnyLt>(rhs) { }
|
||||
static const char* Desc() { return "is <"; }
|
||||
static const char* NegatedDesc() { return "isn't <"; }
|
||||
};
|
||||
template <typename Rhs>
|
||||
class GtMatcher : public ComparisonBase<GtMatcher<Rhs>, Rhs, AnyGt> {
|
||||
public:
|
||||
explicit GtMatcher(const Rhs& rhs)
|
||||
: ComparisonBase<GtMatcher<Rhs>, Rhs, AnyGt>(rhs) { }
|
||||
static const char* Desc() { return "is >"; }
|
||||
static const char* NegatedDesc() { return "isn't >"; }
|
||||
};
|
||||
template <typename Rhs>
|
||||
class LeMatcher : public ComparisonBase<LeMatcher<Rhs>, Rhs, AnyLe> {
|
||||
public:
|
||||
explicit LeMatcher(const Rhs& rhs)
|
||||
: ComparisonBase<LeMatcher<Rhs>, Rhs, AnyLe>(rhs) { }
|
||||
static const char* Desc() { return "is <="; }
|
||||
static const char* NegatedDesc() { return "isn't <="; }
|
||||
};
|
||||
template <typename Rhs>
|
||||
class GeMatcher : public ComparisonBase<GeMatcher<Rhs>, Rhs, AnyGe> {
|
||||
public:
|
||||
explicit GeMatcher(const Rhs& rhs)
|
||||
: ComparisonBase<GeMatcher<Rhs>, Rhs, AnyGe>(rhs) { }
|
||||
static const char* Desc() { return "is >="; }
|
||||
static const char* NegatedDesc() { return "isn't >="; }
|
||||
};
|
||||
|
||||
template <typename T, typename = typename std::enable_if<
|
||||
std::is_constructible<std::string, T>::value>::type>
|
||||
using StringLike = T;
|
||||
|
||||
// Implements polymorphic matchers MatchesRegex(regex) and
|
||||
// ContainsRegex(regex), which can be used as a Matcher<T> as long as
|
||||
// T can be converted to a string.
|
||||
class MatchesRegexMatcher {
|
||||
public:
|
||||
MatchesRegexMatcher(const RE* regex, bool full_match)
|
||||
: regex_(regex), full_match_(full_match) {}
|
||||
|
||||
#if GTEST_INTERNAL_HAS_STRING_VIEW
|
||||
bool MatchAndExplain(const internal::StringView& s,
|
||||
MatchResultListener* listener) const {
|
||||
return MatchAndExplain(std::string(s), listener);
|
||||
}
|
||||
#endif // GTEST_INTERNAL_HAS_STRING_VIEW
|
||||
|
||||
// Accepts pointer types, particularly:
|
||||
// const char*
|
||||
// char*
|
||||
// const wchar_t*
|
||||
// wchar_t*
|
||||
template <typename CharType>
|
||||
bool MatchAndExplain(CharType* s, MatchResultListener* listener) const {
|
||||
return s != nullptr && MatchAndExplain(std::string(s), listener);
|
||||
}
|
||||
|
||||
// Matches anything that can convert to std::string.
|
||||
//
|
||||
// This is a template, not just a plain function with const std::string&,
|
||||
// because absl::string_view has some interfering non-explicit constructors.
|
||||
template <class MatcheeStringType>
|
||||
bool MatchAndExplain(const MatcheeStringType& s,
|
||||
MatchResultListener* /* listener */) const {
|
||||
const std::string& s2(s);
|
||||
return full_match_ ? RE::FullMatch(s2, *regex_)
|
||||
: RE::PartialMatch(s2, *regex_);
|
||||
}
|
||||
|
||||
void DescribeTo(::std::ostream* os) const {
|
||||
*os << (full_match_ ? "matches" : "contains") << " regular expression ";
|
||||
UniversalPrinter<std::string>::Print(regex_->pattern(), os);
|
||||
}
|
||||
|
||||
void DescribeNegationTo(::std::ostream* os) const {
|
||||
*os << "doesn't " << (full_match_ ? "match" : "contain")
|
||||
<< " regular expression ";
|
||||
UniversalPrinter<std::string>::Print(regex_->pattern(), os);
|
||||
}
|
||||
|
||||
private:
|
||||
const std::shared_ptr<const RE> regex_;
|
||||
const bool full_match_;
|
||||
};
|
||||
} // namespace internal
|
||||
|
||||
// Matches a string that fully matches regular expression 'regex'.
|
||||
// The matcher takes ownership of 'regex'.
|
||||
inline PolymorphicMatcher<internal::MatchesRegexMatcher> MatchesRegex(
|
||||
const internal::RE* regex) {
|
||||
return MakePolymorphicMatcher(internal::MatchesRegexMatcher(regex, true));
|
||||
}
|
||||
template <typename T = std::string>
|
||||
PolymorphicMatcher<internal::MatchesRegexMatcher> MatchesRegex(
|
||||
const internal::StringLike<T>& regex) {
|
||||
return MatchesRegex(new internal::RE(std::string(regex)));
|
||||
}
|
||||
|
||||
// Matches a string that contains regular expression 'regex'.
|
||||
// The matcher takes ownership of 'regex'.
|
||||
inline PolymorphicMatcher<internal::MatchesRegexMatcher> ContainsRegex(
|
||||
const internal::RE* regex) {
|
||||
return MakePolymorphicMatcher(internal::MatchesRegexMatcher(regex, false));
|
||||
}
|
||||
template <typename T = std::string>
|
||||
PolymorphicMatcher<internal::MatchesRegexMatcher> ContainsRegex(
|
||||
const internal::StringLike<T>& regex) {
|
||||
return ContainsRegex(new internal::RE(std::string(regex)));
|
||||
}
|
||||
|
||||
// Creates a polymorphic matcher that matches anything equal to x.
|
||||
// Note: if the parameter of Eq() were declared as const T&, Eq("foo")
|
||||
// wouldn't compile.
|
||||
template <typename T>
|
||||
inline internal::EqMatcher<T> Eq(T x) { return internal::EqMatcher<T>(x); }
|
||||
|
||||
// Constructs a Matcher<T> from a 'value' of type T. The constructed
|
||||
// matcher matches any value that's equal to 'value'.
|
||||
template <typename T>
|
||||
Matcher<T>::Matcher(T value) { *this = Eq(value); }
|
||||
|
||||
// Creates a monomorphic matcher that matches anything with type Lhs
|
||||
// and equal to rhs. A user may need to use this instead of Eq(...)
|
||||
// in order to resolve an overloading ambiguity.
|
||||
//
|
||||
// TypedEq<T>(x) is just a convenient short-hand for Matcher<T>(Eq(x))
|
||||
// or Matcher<T>(x), but more readable than the latter.
|
||||
//
|
||||
// We could define similar monomorphic matchers for other comparison
|
||||
// operations (e.g. TypedLt, TypedGe, and etc), but decided not to do
|
||||
// it yet as those are used much less than Eq() in practice. A user
|
||||
// can always write Matcher<T>(Lt(5)) to be explicit about the type,
|
||||
// for example.
|
||||
template <typename Lhs, typename Rhs>
|
||||
inline Matcher<Lhs> TypedEq(const Rhs& rhs) { return Eq(rhs); }
|
||||
|
||||
// Creates a polymorphic matcher that matches anything >= x.
|
||||
template <typename Rhs>
|
||||
inline internal::GeMatcher<Rhs> Ge(Rhs x) {
|
||||
return internal::GeMatcher<Rhs>(x);
|
||||
}
|
||||
|
||||
// Creates a polymorphic matcher that matches anything > x.
|
||||
template <typename Rhs>
|
||||
inline internal::GtMatcher<Rhs> Gt(Rhs x) {
|
||||
return internal::GtMatcher<Rhs>(x);
|
||||
}
|
||||
|
||||
// Creates a polymorphic matcher that matches anything <= x.
|
||||
template <typename Rhs>
|
||||
inline internal::LeMatcher<Rhs> Le(Rhs x) {
|
||||
return internal::LeMatcher<Rhs>(x);
|
||||
}
|
||||
|
||||
// Creates a polymorphic matcher that matches anything < x.
|
||||
template <typename Rhs>
|
||||
inline internal::LtMatcher<Rhs> Lt(Rhs x) {
|
||||
return internal::LtMatcher<Rhs>(x);
|
||||
}
|
||||
|
||||
// Creates a polymorphic matcher that matches anything != x.
|
||||
template <typename Rhs>
|
||||
inline internal::NeMatcher<Rhs> Ne(Rhs x) {
|
||||
return internal::NeMatcher<Rhs>(x);
|
||||
}
|
||||
} // namespace testing
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 5046
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_MATCHERS_H_
|
||||
@@ -0,0 +1,219 @@
|
||||
// Copyright 2005, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// The Google C++ Testing and Mocking Framework (Google Test)
|
||||
//
|
||||
// This header file defines the Message class.
|
||||
//
|
||||
// IMPORTANT NOTE: Due to limitation of the C++ language, we have to
|
||||
// leave some internal implementation details in this header file.
|
||||
// They are clearly marked by comments like this:
|
||||
//
|
||||
// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
|
||||
//
|
||||
// Such code is NOT meant to be used by a user directly, and is subject
|
||||
// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user
|
||||
// program!
|
||||
|
||||
// GOOGLETEST_CM0001 DO NOT DELETE
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_MESSAGE_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_MESSAGE_H_
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
|
||||
/* class A needs to have dll-interface to be used by clients of class B */)
|
||||
|
||||
// Ensures that there is at least one operator<< in the global namespace.
|
||||
// See Message& operator<<(...) below for why.
|
||||
void operator<<(const testing::internal::Secret&, int);
|
||||
|
||||
namespace testing {
|
||||
|
||||
// The Message class works like an ostream repeater.
|
||||
//
|
||||
// Typical usage:
|
||||
//
|
||||
// 1. You stream a bunch of values to a Message object.
|
||||
// It will remember the text in a stringstream.
|
||||
// 2. Then you stream the Message object to an ostream.
|
||||
// This causes the text in the Message to be streamed
|
||||
// to the ostream.
|
||||
//
|
||||
// For example;
|
||||
//
|
||||
// testing::Message foo;
|
||||
// foo << 1 << " != " << 2;
|
||||
// std::cout << foo;
|
||||
//
|
||||
// will print "1 != 2".
|
||||
//
|
||||
// Message is not intended to be inherited from. In particular, its
|
||||
// destructor is not virtual.
|
||||
//
|
||||
// Note that stringstream behaves differently in gcc and in MSVC. You
|
||||
// can stream a NULL char pointer to it in the former, but not in the
|
||||
// latter (it causes an access violation if you do). The Message
|
||||
// class hides this difference by treating a NULL char pointer as
|
||||
// "(null)".
|
||||
class GTEST_API_ Message {
|
||||
private:
|
||||
// The type of basic IO manipulators (endl, ends, and flush) for
|
||||
// narrow streams.
|
||||
typedef std::ostream& (*BasicNarrowIoManip)(std::ostream&);
|
||||
|
||||
public:
|
||||
// Constructs an empty Message.
|
||||
Message();
|
||||
|
||||
// Copy constructor.
|
||||
Message(const Message& msg) : ss_(new ::std::stringstream) { // NOLINT
|
||||
*ss_ << msg.GetString();
|
||||
}
|
||||
|
||||
// Constructs a Message from a C-string.
|
||||
explicit Message(const char* str) : ss_(new ::std::stringstream) {
|
||||
*ss_ << str;
|
||||
}
|
||||
|
||||
// Streams a non-pointer value to this object.
|
||||
template <typename T>
|
||||
inline Message& operator <<(const T& val) {
|
||||
// Some libraries overload << for STL containers. These
|
||||
// overloads are defined in the global namespace instead of ::std.
|
||||
//
|
||||
// C++'s symbol lookup rule (i.e. Koenig lookup) says that these
|
||||
// overloads are visible in either the std namespace or the global
|
||||
// namespace, but not other namespaces, including the testing
|
||||
// namespace which Google Test's Message class is in.
|
||||
//
|
||||
// To allow STL containers (and other types that has a << operator
|
||||
// defined in the global namespace) to be used in Google Test
|
||||
// assertions, testing::Message must access the custom << operator
|
||||
// from the global namespace. With this using declaration,
|
||||
// overloads of << defined in the global namespace and those
|
||||
// visible via Koenig lookup are both exposed in this function.
|
||||
using ::operator <<;
|
||||
*ss_ << val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Streams a pointer value to this object.
|
||||
//
|
||||
// This function is an overload of the previous one. When you
|
||||
// stream a pointer to a Message, this definition will be used as it
|
||||
// is more specialized. (The C++ Standard, section
|
||||
// [temp.func.order].) If you stream a non-pointer, then the
|
||||
// previous definition will be used.
|
||||
//
|
||||
// The reason for this overload is that streaming a NULL pointer to
|
||||
// ostream is undefined behavior. Depending on the compiler, you
|
||||
// may get "0", "(nil)", "(null)", or an access violation. To
|
||||
// ensure consistent result across compilers, we always treat NULL
|
||||
// as "(null)".
|
||||
template <typename T>
|
||||
inline Message& operator <<(T* const& pointer) { // NOLINT
|
||||
if (pointer == nullptr) {
|
||||
*ss_ << "(null)";
|
||||
} else {
|
||||
*ss_ << pointer;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Since the basic IO manipulators are overloaded for both narrow
|
||||
// and wide streams, we have to provide this specialized definition
|
||||
// of operator <<, even though its body is the same as the
|
||||
// templatized version above. Without this definition, streaming
|
||||
// endl or other basic IO manipulators to Message will confuse the
|
||||
// compiler.
|
||||
Message& operator <<(BasicNarrowIoManip val) {
|
||||
*ss_ << val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Instead of 1/0, we want to see true/false for bool values.
|
||||
Message& operator <<(bool b) {
|
||||
return *this << (b ? "true" : "false");
|
||||
}
|
||||
|
||||
// These two overloads allow streaming a wide C string to a Message
|
||||
// using the UTF-8 encoding.
|
||||
Message& operator <<(const wchar_t* wide_c_str);
|
||||
Message& operator <<(wchar_t* wide_c_str);
|
||||
|
||||
#if GTEST_HAS_STD_WSTRING
|
||||
// Converts the given wide string to a narrow string using the UTF-8
|
||||
// encoding, and streams the result to this Message object.
|
||||
Message& operator <<(const ::std::wstring& wstr);
|
||||
#endif // GTEST_HAS_STD_WSTRING
|
||||
|
||||
// Gets the text streamed to this object so far as an std::string.
|
||||
// Each '\0' character in the buffer is replaced with "\\0".
|
||||
//
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
|
||||
std::string GetString() const;
|
||||
|
||||
private:
|
||||
// We'll hold the text streamed to this object here.
|
||||
const std::unique_ptr< ::std::stringstream> ss_;
|
||||
|
||||
// We declare (but don't implement) this to prevent the compiler
|
||||
// from implementing the assignment operator.
|
||||
void operator=(const Message&);
|
||||
};
|
||||
|
||||
// Streams a Message to an ostream.
|
||||
inline std::ostream& operator <<(std::ostream& os, const Message& sb) {
|
||||
return os << sb.GetString();
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Converts a streamable value to an std::string. A NULL pointer is
|
||||
// converted to "(null)". When the input value is a ::string,
|
||||
// ::std::string, ::wstring, or ::std::wstring object, each NUL
|
||||
// character in it is replaced with "\\0".
|
||||
template <typename T>
|
||||
std::string StreamableToString(const T& streamable) {
|
||||
return (Message() << streamable).GetString();
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace testing
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_MESSAGE_H_
|
||||
@@ -0,0 +1,507 @@
|
||||
// Copyright 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Macros and functions for implementing parameterized tests
|
||||
// in Google C++ Testing and Mocking Framework (Google Test)
|
||||
//
|
||||
// GOOGLETEST_CM0001 DO NOT DELETE
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_
|
||||
|
||||
// Value-parameterized tests allow you to test your code with different
|
||||
// parameters without writing multiple copies of the same test.
|
||||
//
|
||||
// Here is how you use value-parameterized tests:
|
||||
|
||||
#if 0
|
||||
|
||||
// To write value-parameterized tests, first you should define a fixture
|
||||
// class. It is usually derived from testing::TestWithParam<T> (see below for
|
||||
// another inheritance scheme that's sometimes useful in more complicated
|
||||
// class hierarchies), where the type of your parameter values.
|
||||
// TestWithParam<T> is itself derived from testing::Test. T can be any
|
||||
// copyable type. If it's a raw pointer, you are responsible for managing the
|
||||
// lifespan of the pointed values.
|
||||
|
||||
class FooTest : public ::testing::TestWithParam<const char*> {
|
||||
// You can implement all the usual class fixture members here.
|
||||
};
|
||||
|
||||
// Then, use the TEST_P macro to define as many parameterized tests
|
||||
// for this fixture as you want. The _P suffix is for "parameterized"
|
||||
// or "pattern", whichever you prefer to think.
|
||||
|
||||
TEST_P(FooTest, DoesBlah) {
|
||||
// Inside a test, access the test parameter with the GetParam() method
|
||||
// of the TestWithParam<T> class:
|
||||
EXPECT_TRUE(foo.Blah(GetParam()));
|
||||
...
|
||||
}
|
||||
|
||||
TEST_P(FooTest, HasBlahBlah) {
|
||||
...
|
||||
}
|
||||
|
||||
// Finally, you can use INSTANTIATE_TEST_SUITE_P to instantiate the test
|
||||
// case with any set of parameters you want. Google Test defines a number
|
||||
// of functions for generating test parameters. They return what we call
|
||||
// (surprise!) parameter generators. Here is a summary of them, which
|
||||
// are all in the testing namespace:
|
||||
//
|
||||
//
|
||||
// Range(begin, end [, step]) - Yields values {begin, begin+step,
|
||||
// begin+step+step, ...}. The values do not
|
||||
// include end. step defaults to 1.
|
||||
// Values(v1, v2, ..., vN) - Yields values {v1, v2, ..., vN}.
|
||||
// ValuesIn(container) - Yields values from a C-style array, an STL
|
||||
// ValuesIn(begin,end) container, or an iterator range [begin, end).
|
||||
// Bool() - Yields sequence {false, true}.
|
||||
// Combine(g1, g2, ..., gN) - Yields all combinations (the Cartesian product
|
||||
// for the math savvy) of the values generated
|
||||
// by the N generators.
|
||||
//
|
||||
// For more details, see comments at the definitions of these functions below
|
||||
// in this file.
|
||||
//
|
||||
// The following statement will instantiate tests from the FooTest test suite
|
||||
// each with parameter values "meeny", "miny", and "moe".
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(InstantiationName,
|
||||
FooTest,
|
||||
Values("meeny", "miny", "moe"));
|
||||
|
||||
// To distinguish different instances of the pattern, (yes, you
|
||||
// can instantiate it more than once) the first argument to the
|
||||
// INSTANTIATE_TEST_SUITE_P macro is a prefix that will be added to the
|
||||
// actual test suite name. Remember to pick unique prefixes for different
|
||||
// instantiations. The tests from the instantiation above will have
|
||||
// these names:
|
||||
//
|
||||
// * InstantiationName/FooTest.DoesBlah/0 for "meeny"
|
||||
// * InstantiationName/FooTest.DoesBlah/1 for "miny"
|
||||
// * InstantiationName/FooTest.DoesBlah/2 for "moe"
|
||||
// * InstantiationName/FooTest.HasBlahBlah/0 for "meeny"
|
||||
// * InstantiationName/FooTest.HasBlahBlah/1 for "miny"
|
||||
// * InstantiationName/FooTest.HasBlahBlah/2 for "moe"
|
||||
//
|
||||
// You can use these names in --gtest_filter.
|
||||
//
|
||||
// This statement will instantiate all tests from FooTest again, each
|
||||
// with parameter values "cat" and "dog":
|
||||
|
||||
const char* pets[] = {"cat", "dog"};
|
||||
INSTANTIATE_TEST_SUITE_P(AnotherInstantiationName, FooTest, ValuesIn(pets));
|
||||
|
||||
// The tests from the instantiation above will have these names:
|
||||
//
|
||||
// * AnotherInstantiationName/FooTest.DoesBlah/0 for "cat"
|
||||
// * AnotherInstantiationName/FooTest.DoesBlah/1 for "dog"
|
||||
// * AnotherInstantiationName/FooTest.HasBlahBlah/0 for "cat"
|
||||
// * AnotherInstantiationName/FooTest.HasBlahBlah/1 for "dog"
|
||||
//
|
||||
// Please note that INSTANTIATE_TEST_SUITE_P will instantiate all tests
|
||||
// in the given test suite, whether their definitions come before or
|
||||
// AFTER the INSTANTIATE_TEST_SUITE_P statement.
|
||||
//
|
||||
// Please also note that generator expressions (including parameters to the
|
||||
// generators) are evaluated in InitGoogleTest(), after main() has started.
|
||||
// This allows the user on one hand, to adjust generator parameters in order
|
||||
// to dynamically determine a set of tests to run and on the other hand,
|
||||
// give the user a chance to inspect the generated tests with Google Test
|
||||
// reflection API before RUN_ALL_TESTS() is executed.
|
||||
//
|
||||
// You can see samples/sample7_unittest.cc and samples/sample8_unittest.cc
|
||||
// for more examples.
|
||||
//
|
||||
// In the future, we plan to publish the API for defining new parameter
|
||||
// generators. But for now this interface remains part of the internal
|
||||
// implementation and is subject to change.
|
||||
//
|
||||
//
|
||||
// A parameterized test fixture must be derived from testing::Test and from
|
||||
// testing::WithParamInterface<T>, where T is the type of the parameter
|
||||
// values. Inheriting from TestWithParam<T> satisfies that requirement because
|
||||
// TestWithParam<T> inherits from both Test and WithParamInterface. In more
|
||||
// complicated hierarchies, however, it is occasionally useful to inherit
|
||||
// separately from Test and WithParamInterface. For example:
|
||||
|
||||
class BaseTest : public ::testing::Test {
|
||||
// You can inherit all the usual members for a non-parameterized test
|
||||
// fixture here.
|
||||
};
|
||||
|
||||
class DerivedTest : public BaseTest, public ::testing::WithParamInterface<int> {
|
||||
// The usual test fixture members go here too.
|
||||
};
|
||||
|
||||
TEST_F(BaseTest, HasFoo) {
|
||||
// This is an ordinary non-parameterized test.
|
||||
}
|
||||
|
||||
TEST_P(DerivedTest, DoesBlah) {
|
||||
// GetParam works just the same here as if you inherit from TestWithParam.
|
||||
EXPECT_TRUE(foo.Blah(GetParam()));
|
||||
}
|
||||
|
||||
#endif // 0
|
||||
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
#include "gtest/internal/gtest-internal.h"
|
||||
#include "gtest/internal/gtest-param-util.h"
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
|
||||
namespace testing {
|
||||
|
||||
// Functions producing parameter generators.
|
||||
//
|
||||
// Google Test uses these generators to produce parameters for value-
|
||||
// parameterized tests. When a parameterized test suite is instantiated
|
||||
// with a particular generator, Google Test creates and runs tests
|
||||
// for each element in the sequence produced by the generator.
|
||||
//
|
||||
// In the following sample, tests from test suite FooTest are instantiated
|
||||
// each three times with parameter values 3, 5, and 8:
|
||||
//
|
||||
// class FooTest : public TestWithParam<int> { ... };
|
||||
//
|
||||
// TEST_P(FooTest, TestThis) {
|
||||
// }
|
||||
// TEST_P(FooTest, TestThat) {
|
||||
// }
|
||||
// INSTANTIATE_TEST_SUITE_P(TestSequence, FooTest, Values(3, 5, 8));
|
||||
//
|
||||
|
||||
// Range() returns generators providing sequences of values in a range.
|
||||
//
|
||||
// Synopsis:
|
||||
// Range(start, end)
|
||||
// - returns a generator producing a sequence of values {start, start+1,
|
||||
// start+2, ..., }.
|
||||
// Range(start, end, step)
|
||||
// - returns a generator producing a sequence of values {start, start+step,
|
||||
// start+step+step, ..., }.
|
||||
// Notes:
|
||||
// * The generated sequences never include end. For example, Range(1, 5)
|
||||
// returns a generator producing a sequence {1, 2, 3, 4}. Range(1, 9, 2)
|
||||
// returns a generator producing {1, 3, 5, 7}.
|
||||
// * start and end must have the same type. That type may be any integral or
|
||||
// floating-point type or a user defined type satisfying these conditions:
|
||||
// * It must be assignable (have operator=() defined).
|
||||
// * It must have operator+() (operator+(int-compatible type) for
|
||||
// two-operand version).
|
||||
// * It must have operator<() defined.
|
||||
// Elements in the resulting sequences will also have that type.
|
||||
// * Condition start < end must be satisfied in order for resulting sequences
|
||||
// to contain any elements.
|
||||
//
|
||||
template <typename T, typename IncrementT>
|
||||
internal::ParamGenerator<T> Range(T start, T end, IncrementT step) {
|
||||
return internal::ParamGenerator<T>(
|
||||
new internal::RangeGenerator<T, IncrementT>(start, end, step));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
internal::ParamGenerator<T> Range(T start, T end) {
|
||||
return Range(start, end, 1);
|
||||
}
|
||||
|
||||
// ValuesIn() function allows generation of tests with parameters coming from
|
||||
// a container.
|
||||
//
|
||||
// Synopsis:
|
||||
// ValuesIn(const T (&array)[N])
|
||||
// - returns a generator producing sequences with elements from
|
||||
// a C-style array.
|
||||
// ValuesIn(const Container& container)
|
||||
// - returns a generator producing sequences with elements from
|
||||
// an STL-style container.
|
||||
// ValuesIn(Iterator begin, Iterator end)
|
||||
// - returns a generator producing sequences with elements from
|
||||
// a range [begin, end) defined by a pair of STL-style iterators. These
|
||||
// iterators can also be plain C pointers.
|
||||
//
|
||||
// Please note that ValuesIn copies the values from the containers
|
||||
// passed in and keeps them to generate tests in RUN_ALL_TESTS().
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// This instantiates tests from test suite StringTest
|
||||
// each with C-string values of "foo", "bar", and "baz":
|
||||
//
|
||||
// const char* strings[] = {"foo", "bar", "baz"};
|
||||
// INSTANTIATE_TEST_SUITE_P(StringSequence, StringTest, ValuesIn(strings));
|
||||
//
|
||||
// This instantiates tests from test suite StlStringTest
|
||||
// each with STL strings with values "a" and "b":
|
||||
//
|
||||
// ::std::vector< ::std::string> GetParameterStrings() {
|
||||
// ::std::vector< ::std::string> v;
|
||||
// v.push_back("a");
|
||||
// v.push_back("b");
|
||||
// return v;
|
||||
// }
|
||||
//
|
||||
// INSTANTIATE_TEST_SUITE_P(CharSequence,
|
||||
// StlStringTest,
|
||||
// ValuesIn(GetParameterStrings()));
|
||||
//
|
||||
//
|
||||
// This will also instantiate tests from CharTest
|
||||
// each with parameter values 'a' and 'b':
|
||||
//
|
||||
// ::std::list<char> GetParameterChars() {
|
||||
// ::std::list<char> list;
|
||||
// list.push_back('a');
|
||||
// list.push_back('b');
|
||||
// return list;
|
||||
// }
|
||||
// ::std::list<char> l = GetParameterChars();
|
||||
// INSTANTIATE_TEST_SUITE_P(CharSequence2,
|
||||
// CharTest,
|
||||
// ValuesIn(l.begin(), l.end()));
|
||||
//
|
||||
template <typename ForwardIterator>
|
||||
internal::ParamGenerator<
|
||||
typename std::iterator_traits<ForwardIterator>::value_type>
|
||||
ValuesIn(ForwardIterator begin, ForwardIterator end) {
|
||||
typedef typename std::iterator_traits<ForwardIterator>::value_type ParamType;
|
||||
return internal::ParamGenerator<ParamType>(
|
||||
new internal::ValuesInIteratorRangeGenerator<ParamType>(begin, end));
|
||||
}
|
||||
|
||||
template <typename T, size_t N>
|
||||
internal::ParamGenerator<T> ValuesIn(const T (&array)[N]) {
|
||||
return ValuesIn(array, array + N);
|
||||
}
|
||||
|
||||
template <class Container>
|
||||
internal::ParamGenerator<typename Container::value_type> ValuesIn(
|
||||
const Container& container) {
|
||||
return ValuesIn(container.begin(), container.end());
|
||||
}
|
||||
|
||||
// Values() allows generating tests from explicitly specified list of
|
||||
// parameters.
|
||||
//
|
||||
// Synopsis:
|
||||
// Values(T v1, T v2, ..., T vN)
|
||||
// - returns a generator producing sequences with elements v1, v2, ..., vN.
|
||||
//
|
||||
// For example, this instantiates tests from test suite BarTest each
|
||||
// with values "one", "two", and "three":
|
||||
//
|
||||
// INSTANTIATE_TEST_SUITE_P(NumSequence,
|
||||
// BarTest,
|
||||
// Values("one", "two", "three"));
|
||||
//
|
||||
// This instantiates tests from test suite BazTest each with values 1, 2, 3.5.
|
||||
// The exact type of values will depend on the type of parameter in BazTest.
|
||||
//
|
||||
// INSTANTIATE_TEST_SUITE_P(FloatingNumbers, BazTest, Values(1, 2, 3.5));
|
||||
//
|
||||
//
|
||||
template <typename... T>
|
||||
internal::ValueArray<T...> Values(T... v) {
|
||||
return internal::ValueArray<T...>(std::move(v)...);
|
||||
}
|
||||
|
||||
// Bool() allows generating tests with parameters in a set of (false, true).
|
||||
//
|
||||
// Synopsis:
|
||||
// Bool()
|
||||
// - returns a generator producing sequences with elements {false, true}.
|
||||
//
|
||||
// It is useful when testing code that depends on Boolean flags. Combinations
|
||||
// of multiple flags can be tested when several Bool()'s are combined using
|
||||
// Combine() function.
|
||||
//
|
||||
// In the following example all tests in the test suite FlagDependentTest
|
||||
// will be instantiated twice with parameters false and true.
|
||||
//
|
||||
// class FlagDependentTest : public testing::TestWithParam<bool> {
|
||||
// virtual void SetUp() {
|
||||
// external_flag = GetParam();
|
||||
// }
|
||||
// }
|
||||
// INSTANTIATE_TEST_SUITE_P(BoolSequence, FlagDependentTest, Bool());
|
||||
//
|
||||
inline internal::ParamGenerator<bool> Bool() {
|
||||
return Values(false, true);
|
||||
}
|
||||
|
||||
// Combine() allows the user to combine two or more sequences to produce
|
||||
// values of a Cartesian product of those sequences' elements.
|
||||
//
|
||||
// Synopsis:
|
||||
// Combine(gen1, gen2, ..., genN)
|
||||
// - returns a generator producing sequences with elements coming from
|
||||
// the Cartesian product of elements from the sequences generated by
|
||||
// gen1, gen2, ..., genN. The sequence elements will have a type of
|
||||
// std::tuple<T1, T2, ..., TN> where T1, T2, ..., TN are the types
|
||||
// of elements from sequences produces by gen1, gen2, ..., genN.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// This will instantiate tests in test suite AnimalTest each one with
|
||||
// the parameter values tuple("cat", BLACK), tuple("cat", WHITE),
|
||||
// tuple("dog", BLACK), and tuple("dog", WHITE):
|
||||
//
|
||||
// enum Color { BLACK, GRAY, WHITE };
|
||||
// class AnimalTest
|
||||
// : public testing::TestWithParam<std::tuple<const char*, Color> > {...};
|
||||
//
|
||||
// TEST_P(AnimalTest, AnimalLooksNice) {...}
|
||||
//
|
||||
// INSTANTIATE_TEST_SUITE_P(AnimalVariations, AnimalTest,
|
||||
// Combine(Values("cat", "dog"),
|
||||
// Values(BLACK, WHITE)));
|
||||
//
|
||||
// This will instantiate tests in FlagDependentTest with all variations of two
|
||||
// Boolean flags:
|
||||
//
|
||||
// class FlagDependentTest
|
||||
// : public testing::TestWithParam<std::tuple<bool, bool> > {
|
||||
// virtual void SetUp() {
|
||||
// // Assigns external_flag_1 and external_flag_2 values from the tuple.
|
||||
// std::tie(external_flag_1, external_flag_2) = GetParam();
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// TEST_P(FlagDependentTest, TestFeature1) {
|
||||
// // Test your code using external_flag_1 and external_flag_2 here.
|
||||
// }
|
||||
// INSTANTIATE_TEST_SUITE_P(TwoBoolSequence, FlagDependentTest,
|
||||
// Combine(Bool(), Bool()));
|
||||
//
|
||||
template <typename... Generator>
|
||||
internal::CartesianProductHolder<Generator...> Combine(const Generator&... g) {
|
||||
return internal::CartesianProductHolder<Generator...>(g...);
|
||||
}
|
||||
|
||||
#define TEST_P(test_suite_name, test_name) \
|
||||
class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
|
||||
: public test_suite_name { \
|
||||
public: \
|
||||
GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() {} \
|
||||
void TestBody() override; \
|
||||
\
|
||||
private: \
|
||||
static int AddToRegistry() { \
|
||||
::testing::UnitTest::GetInstance() \
|
||||
->parameterized_test_registry() \
|
||||
.GetTestSuitePatternHolder<test_suite_name>( \
|
||||
GTEST_STRINGIFY_(test_suite_name), \
|
||||
::testing::internal::CodeLocation(__FILE__, __LINE__)) \
|
||||
->AddTestPattern( \
|
||||
GTEST_STRINGIFY_(test_suite_name), GTEST_STRINGIFY_(test_name), \
|
||||
new ::testing::internal::TestMetaFactory<GTEST_TEST_CLASS_NAME_( \
|
||||
test_suite_name, test_name)>(), \
|
||||
::testing::internal::CodeLocation(__FILE__, __LINE__)); \
|
||||
return 0; \
|
||||
} \
|
||||
static int gtest_registering_dummy_ GTEST_ATTRIBUTE_UNUSED_; \
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(GTEST_TEST_CLASS_NAME_(test_suite_name, \
|
||||
test_name)); \
|
||||
}; \
|
||||
int GTEST_TEST_CLASS_NAME_(test_suite_name, \
|
||||
test_name)::gtest_registering_dummy_ = \
|
||||
GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::AddToRegistry(); \
|
||||
void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody()
|
||||
|
||||
// The last argument to INSTANTIATE_TEST_SUITE_P allows the user to specify
|
||||
// generator and an optional function or functor that generates custom test name
|
||||
// suffixes based on the test parameters. Such a function or functor should
|
||||
// accept one argument of type testing::TestParamInfo<class ParamType>, and
|
||||
// return std::string.
|
||||
//
|
||||
// testing::PrintToStringParamName is a builtin test suffix generator that
|
||||
// returns the value of testing::PrintToString(GetParam()).
|
||||
//
|
||||
// Note: test names must be non-empty, unique, and may only contain ASCII
|
||||
// alphanumeric characters or underscore. Because PrintToString adds quotes
|
||||
// to std::string and C strings, it won't work for these types.
|
||||
|
||||
#define GTEST_EXPAND_(arg) arg
|
||||
#define GTEST_GET_FIRST_(first, ...) first
|
||||
#define GTEST_GET_SECOND_(first, second, ...) second
|
||||
|
||||
#define INSTANTIATE_TEST_SUITE_P(prefix, test_suite_name, ...) \
|
||||
static ::testing::internal::ParamGenerator<test_suite_name::ParamType> \
|
||||
gtest_##prefix##test_suite_name##_EvalGenerator_() { \
|
||||
return GTEST_EXPAND_(GTEST_GET_FIRST_(__VA_ARGS__, DUMMY_PARAM_)); \
|
||||
} \
|
||||
static ::std::string gtest_##prefix##test_suite_name##_EvalGenerateName_( \
|
||||
const ::testing::TestParamInfo<test_suite_name::ParamType>& info) { \
|
||||
if (::testing::internal::AlwaysFalse()) { \
|
||||
::testing::internal::TestNotEmpty(GTEST_EXPAND_(GTEST_GET_SECOND_( \
|
||||
__VA_ARGS__, \
|
||||
::testing::internal::DefaultParamName<test_suite_name::ParamType>, \
|
||||
DUMMY_PARAM_))); \
|
||||
auto t = std::make_tuple(__VA_ARGS__); \
|
||||
static_assert(std::tuple_size<decltype(t)>::value <= 2, \
|
||||
"Too Many Args!"); \
|
||||
} \
|
||||
return ((GTEST_EXPAND_(GTEST_GET_SECOND_( \
|
||||
__VA_ARGS__, \
|
||||
::testing::internal::DefaultParamName<test_suite_name::ParamType>, \
|
||||
DUMMY_PARAM_))))(info); \
|
||||
} \
|
||||
static int gtest_##prefix##test_suite_name##_dummy_ \
|
||||
GTEST_ATTRIBUTE_UNUSED_ = \
|
||||
::testing::UnitTest::GetInstance() \
|
||||
->parameterized_test_registry() \
|
||||
.GetTestSuitePatternHolder<test_suite_name>( \
|
||||
GTEST_STRINGIFY_(test_suite_name), \
|
||||
::testing::internal::CodeLocation(__FILE__, __LINE__)) \
|
||||
->AddTestSuiteInstantiation( \
|
||||
GTEST_STRINGIFY_(prefix), \
|
||||
>est_##prefix##test_suite_name##_EvalGenerator_, \
|
||||
>est_##prefix##test_suite_name##_EvalGenerateName_, \
|
||||
__FILE__, __LINE__)
|
||||
|
||||
|
||||
// Allow Marking a Parameterized test class as not needing to be instantiated.
|
||||
#define GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(T) \
|
||||
namespace gtest_do_not_use_outside_namespace_scope {} \
|
||||
static const ::testing::internal::MarkAsIgnored gtest_allow_ignore_##T( \
|
||||
GTEST_STRINGIFY_(T))
|
||||
|
||||
// Legacy API is deprecated but still available
|
||||
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
#define INSTANTIATE_TEST_CASE_P \
|
||||
static_assert(::testing::internal::InstantiateTestCase_P_IsDeprecated(), \
|
||||
""); \
|
||||
INSTANTIATE_TEST_SUITE_P
|
||||
#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
|
||||
} // namespace testing
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,238 @@
|
||||
// Copyright 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// Utilities for testing Google Test itself and code that uses Google Test
|
||||
// (e.g. frameworks built on top of Google Test).
|
||||
|
||||
// GOOGLETEST_CM0004 DO NOT DELETE
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
|
||||
/* class A needs to have dll-interface to be used by clients of class B */)
|
||||
|
||||
namespace testing {
|
||||
|
||||
// This helper class can be used to mock out Google Test failure reporting
|
||||
// so that we can test Google Test or code that builds on Google Test.
|
||||
//
|
||||
// An object of this class appends a TestPartResult object to the
|
||||
// TestPartResultArray object given in the constructor whenever a Google Test
|
||||
// failure is reported. It can either intercept only failures that are
|
||||
// generated in the same thread that created this object or it can intercept
|
||||
// all generated failures. The scope of this mock object can be controlled with
|
||||
// the second argument to the two arguments constructor.
|
||||
class GTEST_API_ ScopedFakeTestPartResultReporter
|
||||
: public TestPartResultReporterInterface {
|
||||
public:
|
||||
// The two possible mocking modes of this object.
|
||||
enum InterceptMode {
|
||||
INTERCEPT_ONLY_CURRENT_THREAD, // Intercepts only thread local failures.
|
||||
INTERCEPT_ALL_THREADS // Intercepts all failures.
|
||||
};
|
||||
|
||||
// The c'tor sets this object as the test part result reporter used
|
||||
// by Google Test. The 'result' parameter specifies where to report the
|
||||
// results. This reporter will only catch failures generated in the current
|
||||
// thread. DEPRECATED
|
||||
explicit ScopedFakeTestPartResultReporter(TestPartResultArray* result);
|
||||
|
||||
// Same as above, but you can choose the interception scope of this object.
|
||||
ScopedFakeTestPartResultReporter(InterceptMode intercept_mode,
|
||||
TestPartResultArray* result);
|
||||
|
||||
// The d'tor restores the previous test part result reporter.
|
||||
~ScopedFakeTestPartResultReporter() override;
|
||||
|
||||
// Appends the TestPartResult object to the TestPartResultArray
|
||||
// received in the constructor.
|
||||
//
|
||||
// This method is from the TestPartResultReporterInterface
|
||||
// interface.
|
||||
void ReportTestPartResult(const TestPartResult& result) override;
|
||||
|
||||
private:
|
||||
void Init();
|
||||
|
||||
const InterceptMode intercept_mode_;
|
||||
TestPartResultReporterInterface* old_reporter_;
|
||||
TestPartResultArray* const result_;
|
||||
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedFakeTestPartResultReporter);
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
// A helper class for implementing EXPECT_FATAL_FAILURE() and
|
||||
// EXPECT_NONFATAL_FAILURE(). Its destructor verifies that the given
|
||||
// TestPartResultArray contains exactly one failure that has the given
|
||||
// type and contains the given substring. If that's not the case, a
|
||||
// non-fatal failure will be generated.
|
||||
class GTEST_API_ SingleFailureChecker {
|
||||
public:
|
||||
// The constructor remembers the arguments.
|
||||
SingleFailureChecker(const TestPartResultArray* results,
|
||||
TestPartResult::Type type, const std::string& substr);
|
||||
~SingleFailureChecker();
|
||||
private:
|
||||
const TestPartResultArray* const results_;
|
||||
const TestPartResult::Type type_;
|
||||
const std::string substr_;
|
||||
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace testing
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
|
||||
|
||||
// A set of macros for testing Google Test assertions or code that's expected
|
||||
// to generate Google Test fatal failures. It verifies that the given
|
||||
// statement will cause exactly one fatal Google Test failure with 'substr'
|
||||
// being part of the failure message.
|
||||
//
|
||||
// There are two different versions of this macro. EXPECT_FATAL_FAILURE only
|
||||
// affects and considers failures generated in the current thread and
|
||||
// EXPECT_FATAL_FAILURE_ON_ALL_THREADS does the same but for all threads.
|
||||
//
|
||||
// The verification of the assertion is done correctly even when the statement
|
||||
// throws an exception or aborts the current function.
|
||||
//
|
||||
// Known restrictions:
|
||||
// - 'statement' cannot reference local non-static variables or
|
||||
// non-static members of the current object.
|
||||
// - 'statement' cannot return a value.
|
||||
// - You cannot stream a failure message to this macro.
|
||||
//
|
||||
// Note that even though the implementations of the following two
|
||||
// macros are much alike, we cannot refactor them to use a common
|
||||
// helper macro, due to some peculiarity in how the preprocessor
|
||||
// works. The AcceptsMacroThatExpandsToUnprotectedComma test in
|
||||
// gtest_unittest.cc will fail to compile if we do that.
|
||||
#define EXPECT_FATAL_FAILURE(statement, substr) \
|
||||
do { \
|
||||
class GTestExpectFatalFailureHelper {\
|
||||
public:\
|
||||
static void Execute() { statement; }\
|
||||
};\
|
||||
::testing::TestPartResultArray gtest_failures;\
|
||||
::testing::internal::SingleFailureChecker gtest_checker(\
|
||||
>est_failures, ::testing::TestPartResult::kFatalFailure, (substr));\
|
||||
{\
|
||||
::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
|
||||
::testing::ScopedFakeTestPartResultReporter:: \
|
||||
INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\
|
||||
GTestExpectFatalFailureHelper::Execute();\
|
||||
}\
|
||||
} while (::testing::internal::AlwaysFalse())
|
||||
|
||||
#define EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substr) \
|
||||
do { \
|
||||
class GTestExpectFatalFailureHelper {\
|
||||
public:\
|
||||
static void Execute() { statement; }\
|
||||
};\
|
||||
::testing::TestPartResultArray gtest_failures;\
|
||||
::testing::internal::SingleFailureChecker gtest_checker(\
|
||||
>est_failures, ::testing::TestPartResult::kFatalFailure, (substr));\
|
||||
{\
|
||||
::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
|
||||
::testing::ScopedFakeTestPartResultReporter:: \
|
||||
INTERCEPT_ALL_THREADS, >est_failures);\
|
||||
GTestExpectFatalFailureHelper::Execute();\
|
||||
}\
|
||||
} while (::testing::internal::AlwaysFalse())
|
||||
|
||||
// A macro for testing Google Test assertions or code that's expected to
|
||||
// generate Google Test non-fatal failures. It asserts that the given
|
||||
// statement will cause exactly one non-fatal Google Test failure with 'substr'
|
||||
// being part of the failure message.
|
||||
//
|
||||
// There are two different versions of this macro. EXPECT_NONFATAL_FAILURE only
|
||||
// affects and considers failures generated in the current thread and
|
||||
// EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS does the same but for all threads.
|
||||
//
|
||||
// 'statement' is allowed to reference local variables and members of
|
||||
// the current object.
|
||||
//
|
||||
// The verification of the assertion is done correctly even when the statement
|
||||
// throws an exception or aborts the current function.
|
||||
//
|
||||
// Known restrictions:
|
||||
// - You cannot stream a failure message to this macro.
|
||||
//
|
||||
// Note that even though the implementations of the following two
|
||||
// macros are much alike, we cannot refactor them to use a common
|
||||
// helper macro, due to some peculiarity in how the preprocessor
|
||||
// works. If we do that, the code won't compile when the user gives
|
||||
// EXPECT_NONFATAL_FAILURE() a statement that contains a macro that
|
||||
// expands to code containing an unprotected comma. The
|
||||
// AcceptsMacroThatExpandsToUnprotectedComma test in gtest_unittest.cc
|
||||
// catches that.
|
||||
//
|
||||
// For the same reason, we have to write
|
||||
// if (::testing::internal::AlwaysTrue()) { statement; }
|
||||
// instead of
|
||||
// GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement)
|
||||
// to avoid an MSVC warning on unreachable code.
|
||||
#define EXPECT_NONFATAL_FAILURE(statement, substr) \
|
||||
do {\
|
||||
::testing::TestPartResultArray gtest_failures;\
|
||||
::testing::internal::SingleFailureChecker gtest_checker(\
|
||||
>est_failures, ::testing::TestPartResult::kNonFatalFailure, \
|
||||
(substr));\
|
||||
{\
|
||||
::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
|
||||
::testing::ScopedFakeTestPartResultReporter:: \
|
||||
INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\
|
||||
if (::testing::internal::AlwaysTrue()) { statement; }\
|
||||
}\
|
||||
} while (::testing::internal::AlwaysFalse())
|
||||
|
||||
#define EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substr) \
|
||||
do {\
|
||||
::testing::TestPartResultArray gtest_failures;\
|
||||
::testing::internal::SingleFailureChecker gtest_checker(\
|
||||
>est_failures, ::testing::TestPartResult::kNonFatalFailure, \
|
||||
(substr));\
|
||||
{\
|
||||
::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
|
||||
::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS, \
|
||||
>est_failures);\
|
||||
if (::testing::internal::AlwaysTrue()) { statement; }\
|
||||
}\
|
||||
} while (::testing::internal::AlwaysFalse())
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
|
||||
@@ -0,0 +1,184 @@
|
||||
// Copyright 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// GOOGLETEST_CM0001 DO NOT DELETE
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_TEST_PART_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_TEST_PART_H_
|
||||
|
||||
#include <iosfwd>
|
||||
#include <vector>
|
||||
#include "gtest/internal/gtest-internal.h"
|
||||
#include "gtest/internal/gtest-string.h"
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
|
||||
/* class A needs to have dll-interface to be used by clients of class B */)
|
||||
|
||||
namespace testing {
|
||||
|
||||
// A copyable object representing the result of a test part (i.e. an
|
||||
// assertion or an explicit FAIL(), ADD_FAILURE(), or SUCCESS()).
|
||||
//
|
||||
// Don't inherit from TestPartResult as its destructor is not virtual.
|
||||
class GTEST_API_ TestPartResult {
|
||||
public:
|
||||
// The possible outcomes of a test part (i.e. an assertion or an
|
||||
// explicit SUCCEED(), FAIL(), or ADD_FAILURE()).
|
||||
enum Type {
|
||||
kSuccess, // Succeeded.
|
||||
kNonFatalFailure, // Failed but the test can continue.
|
||||
kFatalFailure, // Failed and the test should be terminated.
|
||||
kSkip // Skipped.
|
||||
};
|
||||
|
||||
// C'tor. TestPartResult does NOT have a default constructor.
|
||||
// Always use this constructor (with parameters) to create a
|
||||
// TestPartResult object.
|
||||
TestPartResult(Type a_type, const char* a_file_name, int a_line_number,
|
||||
const char* a_message)
|
||||
: type_(a_type),
|
||||
file_name_(a_file_name == nullptr ? "" : a_file_name),
|
||||
line_number_(a_line_number),
|
||||
summary_(ExtractSummary(a_message)),
|
||||
message_(a_message) {}
|
||||
|
||||
// Gets the outcome of the test part.
|
||||
Type type() const { return type_; }
|
||||
|
||||
// Gets the name of the source file where the test part took place, or
|
||||
// NULL if it's unknown.
|
||||
const char* file_name() const {
|
||||
return file_name_.empty() ? nullptr : file_name_.c_str();
|
||||
}
|
||||
|
||||
// Gets the line in the source file where the test part took place,
|
||||
// or -1 if it's unknown.
|
||||
int line_number() const { return line_number_; }
|
||||
|
||||
// Gets the summary of the failure message.
|
||||
const char* summary() const { return summary_.c_str(); }
|
||||
|
||||
// Gets the message associated with the test part.
|
||||
const char* message() const { return message_.c_str(); }
|
||||
|
||||
// Returns true if and only if the test part was skipped.
|
||||
bool skipped() const { return type_ == kSkip; }
|
||||
|
||||
// Returns true if and only if the test part passed.
|
||||
bool passed() const { return type_ == kSuccess; }
|
||||
|
||||
// Returns true if and only if the test part non-fatally failed.
|
||||
bool nonfatally_failed() const { return type_ == kNonFatalFailure; }
|
||||
|
||||
// Returns true if and only if the test part fatally failed.
|
||||
bool fatally_failed() const { return type_ == kFatalFailure; }
|
||||
|
||||
// Returns true if and only if the test part failed.
|
||||
bool failed() const { return fatally_failed() || nonfatally_failed(); }
|
||||
|
||||
private:
|
||||
Type type_;
|
||||
|
||||
// Gets the summary of the failure message by omitting the stack
|
||||
// trace in it.
|
||||
static std::string ExtractSummary(const char* message);
|
||||
|
||||
// The name of the source file where the test part took place, or
|
||||
// "" if the source file is unknown.
|
||||
std::string file_name_;
|
||||
// The line in the source file where the test part took place, or -1
|
||||
// if the line number is unknown.
|
||||
int line_number_;
|
||||
std::string summary_; // The test failure summary.
|
||||
std::string message_; // The test failure message.
|
||||
};
|
||||
|
||||
// Prints a TestPartResult object.
|
||||
std::ostream& operator<<(std::ostream& os, const TestPartResult& result);
|
||||
|
||||
// An array of TestPartResult objects.
|
||||
//
|
||||
// Don't inherit from TestPartResultArray as its destructor is not
|
||||
// virtual.
|
||||
class GTEST_API_ TestPartResultArray {
|
||||
public:
|
||||
TestPartResultArray() {}
|
||||
|
||||
// Appends the given TestPartResult to the array.
|
||||
void Append(const TestPartResult& result);
|
||||
|
||||
// Returns the TestPartResult at the given index (0-based).
|
||||
const TestPartResult& GetTestPartResult(int index) const;
|
||||
|
||||
// Returns the number of TestPartResult objects in the array.
|
||||
int size() const;
|
||||
|
||||
private:
|
||||
std::vector<TestPartResult> array_;
|
||||
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(TestPartResultArray);
|
||||
};
|
||||
|
||||
// This interface knows how to report a test part result.
|
||||
class GTEST_API_ TestPartResultReporterInterface {
|
||||
public:
|
||||
virtual ~TestPartResultReporterInterface() {}
|
||||
|
||||
virtual void ReportTestPartResult(const TestPartResult& result) = 0;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
// This helper class is used by {ASSERT|EXPECT}_NO_FATAL_FAILURE to check if a
|
||||
// statement generates new fatal failures. To do so it registers itself as the
|
||||
// current test part result reporter. Besides checking if fatal failures were
|
||||
// reported, it only delegates the reporting to the former result reporter.
|
||||
// The original result reporter is restored in the destructor.
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
|
||||
class GTEST_API_ HasNewFatalFailureHelper
|
||||
: public TestPartResultReporterInterface {
|
||||
public:
|
||||
HasNewFatalFailureHelper();
|
||||
~HasNewFatalFailureHelper() override;
|
||||
void ReportTestPartResult(const TestPartResult& result) override;
|
||||
bool has_new_fatal_failure() const { return has_new_fatal_failure_; }
|
||||
private:
|
||||
bool has_new_fatal_failure_;
|
||||
TestPartResultReporterInterface* original_reporter_;
|
||||
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(HasNewFatalFailureHelper);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace testing
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_TEST_PART_H_
|
||||
@@ -0,0 +1,329 @@
|
||||
// Copyright 2008 Google Inc.
|
||||
// All Rights Reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// GOOGLETEST_CM0001 DO NOT DELETE
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_
|
||||
|
||||
// This header implements typed tests and type-parameterized tests.
|
||||
|
||||
// Typed (aka type-driven) tests repeat the same test for types in a
|
||||
// list. You must know which types you want to test with when writing
|
||||
// typed tests. Here's how you do it:
|
||||
|
||||
#if 0
|
||||
|
||||
// First, define a fixture class template. It should be parameterized
|
||||
// by a type. Remember to derive it from testing::Test.
|
||||
template <typename T>
|
||||
class FooTest : public testing::Test {
|
||||
public:
|
||||
...
|
||||
typedef std::list<T> List;
|
||||
static T shared_;
|
||||
T value_;
|
||||
};
|
||||
|
||||
// Next, associate a list of types with the test suite, which will be
|
||||
// repeated for each type in the list. The typedef is necessary for
|
||||
// the macro to parse correctly.
|
||||
typedef testing::Types<char, int, unsigned int> MyTypes;
|
||||
TYPED_TEST_SUITE(FooTest, MyTypes);
|
||||
|
||||
// If the type list contains only one type, you can write that type
|
||||
// directly without Types<...>:
|
||||
// TYPED_TEST_SUITE(FooTest, int);
|
||||
|
||||
// Then, use TYPED_TEST() instead of TEST_F() to define as many typed
|
||||
// tests for this test suite as you want.
|
||||
TYPED_TEST(FooTest, DoesBlah) {
|
||||
// Inside a test, refer to the special name TypeParam to get the type
|
||||
// parameter. Since we are inside a derived class template, C++ requires
|
||||
// us to visit the members of FooTest via 'this'.
|
||||
TypeParam n = this->value_;
|
||||
|
||||
// To visit static members of the fixture, add the TestFixture::
|
||||
// prefix.
|
||||
n += TestFixture::shared_;
|
||||
|
||||
// To refer to typedefs in the fixture, add the "typename
|
||||
// TestFixture::" prefix.
|
||||
typename TestFixture::List values;
|
||||
values.push_back(n);
|
||||
...
|
||||
}
|
||||
|
||||
TYPED_TEST(FooTest, HasPropertyA) { ... }
|
||||
|
||||
// TYPED_TEST_SUITE takes an optional third argument which allows to specify a
|
||||
// class that generates custom test name suffixes based on the type. This should
|
||||
// be a class which has a static template function GetName(int index) returning
|
||||
// a string for each type. The provided integer index equals the index of the
|
||||
// type in the provided type list. In many cases the index can be ignored.
|
||||
//
|
||||
// For example:
|
||||
// class MyTypeNames {
|
||||
// public:
|
||||
// template <typename T>
|
||||
// static std::string GetName(int) {
|
||||
// if (std::is_same<T, char>()) return "char";
|
||||
// if (std::is_same<T, int>()) return "int";
|
||||
// if (std::is_same<T, unsigned int>()) return "unsignedInt";
|
||||
// }
|
||||
// };
|
||||
// TYPED_TEST_SUITE(FooTest, MyTypes, MyTypeNames);
|
||||
|
||||
#endif // 0
|
||||
|
||||
// Type-parameterized tests are abstract test patterns parameterized
|
||||
// by a type. Compared with typed tests, type-parameterized tests
|
||||
// allow you to define the test pattern without knowing what the type
|
||||
// parameters are. The defined pattern can be instantiated with
|
||||
// different types any number of times, in any number of translation
|
||||
// units.
|
||||
//
|
||||
// If you are designing an interface or concept, you can define a
|
||||
// suite of type-parameterized tests to verify properties that any
|
||||
// valid implementation of the interface/concept should have. Then,
|
||||
// each implementation can easily instantiate the test suite to verify
|
||||
// that it conforms to the requirements, without having to write
|
||||
// similar tests repeatedly. Here's an example:
|
||||
|
||||
#if 0
|
||||
|
||||
// First, define a fixture class template. It should be parameterized
|
||||
// by a type. Remember to derive it from testing::Test.
|
||||
template <typename T>
|
||||
class FooTest : public testing::Test {
|
||||
...
|
||||
};
|
||||
|
||||
// Next, declare that you will define a type-parameterized test suite
|
||||
// (the _P suffix is for "parameterized" or "pattern", whichever you
|
||||
// prefer):
|
||||
TYPED_TEST_SUITE_P(FooTest);
|
||||
|
||||
// Then, use TYPED_TEST_P() to define as many type-parameterized tests
|
||||
// for this type-parameterized test suite as you want.
|
||||
TYPED_TEST_P(FooTest, DoesBlah) {
|
||||
// Inside a test, refer to TypeParam to get the type parameter.
|
||||
TypeParam n = 0;
|
||||
...
|
||||
}
|
||||
|
||||
TYPED_TEST_P(FooTest, HasPropertyA) { ... }
|
||||
|
||||
// Now the tricky part: you need to register all test patterns before
|
||||
// you can instantiate them. The first argument of the macro is the
|
||||
// test suite name; the rest are the names of the tests in this test
|
||||
// case.
|
||||
REGISTER_TYPED_TEST_SUITE_P(FooTest,
|
||||
DoesBlah, HasPropertyA);
|
||||
|
||||
// Finally, you are free to instantiate the pattern with the types you
|
||||
// want. If you put the above code in a header file, you can #include
|
||||
// it in multiple C++ source files and instantiate it multiple times.
|
||||
//
|
||||
// To distinguish different instances of the pattern, the first
|
||||
// argument to the INSTANTIATE_* macro is a prefix that will be added
|
||||
// to the actual test suite name. Remember to pick unique prefixes for
|
||||
// different instances.
|
||||
typedef testing::Types<char, int, unsigned int> MyTypes;
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes);
|
||||
|
||||
// If the type list contains only one type, you can write that type
|
||||
// directly without Types<...>:
|
||||
// INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, int);
|
||||
//
|
||||
// Similar to the optional argument of TYPED_TEST_SUITE above,
|
||||
// INSTANTIATE_TEST_SUITE_P takes an optional fourth argument which allows to
|
||||
// generate custom names.
|
||||
// INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes, MyTypeNames);
|
||||
|
||||
#endif // 0
|
||||
|
||||
#include "gtest/internal/gtest-internal.h"
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
#include "gtest/internal/gtest-type-util.h"
|
||||
|
||||
// Implements typed tests.
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
|
||||
//
|
||||
// Expands to the name of the typedef for the type parameters of the
|
||||
// given test suite.
|
||||
#define GTEST_TYPE_PARAMS_(TestSuiteName) gtest_type_params_##TestSuiteName##_
|
||||
|
||||
// Expands to the name of the typedef for the NameGenerator, responsible for
|
||||
// creating the suffixes of the name.
|
||||
#define GTEST_NAME_GENERATOR_(TestSuiteName) \
|
||||
gtest_type_params_##TestSuiteName##_NameGenerator
|
||||
|
||||
#define TYPED_TEST_SUITE(CaseName, Types, ...) \
|
||||
typedef ::testing::internal::GenerateTypeList<Types>::type \
|
||||
GTEST_TYPE_PARAMS_(CaseName); \
|
||||
typedef ::testing::internal::NameGeneratorSelector<__VA_ARGS__>::type \
|
||||
GTEST_NAME_GENERATOR_(CaseName)
|
||||
|
||||
#define TYPED_TEST(CaseName, TestName) \
|
||||
static_assert(sizeof(GTEST_STRINGIFY_(TestName)) > 1, \
|
||||
"test-name must not be empty"); \
|
||||
template <typename gtest_TypeParam_> \
|
||||
class GTEST_TEST_CLASS_NAME_(CaseName, TestName) \
|
||||
: public CaseName<gtest_TypeParam_> { \
|
||||
private: \
|
||||
typedef CaseName<gtest_TypeParam_> TestFixture; \
|
||||
typedef gtest_TypeParam_ TypeParam; \
|
||||
void TestBody() override; \
|
||||
}; \
|
||||
static bool gtest_##CaseName##_##TestName##_registered_ \
|
||||
GTEST_ATTRIBUTE_UNUSED_ = ::testing::internal::TypeParameterizedTest< \
|
||||
CaseName, \
|
||||
::testing::internal::TemplateSel<GTEST_TEST_CLASS_NAME_(CaseName, \
|
||||
TestName)>, \
|
||||
GTEST_TYPE_PARAMS_( \
|
||||
CaseName)>::Register("", \
|
||||
::testing::internal::CodeLocation( \
|
||||
__FILE__, __LINE__), \
|
||||
GTEST_STRINGIFY_(CaseName), \
|
||||
GTEST_STRINGIFY_(TestName), 0, \
|
||||
::testing::internal::GenerateNames< \
|
||||
GTEST_NAME_GENERATOR_(CaseName), \
|
||||
GTEST_TYPE_PARAMS_(CaseName)>()); \
|
||||
template <typename gtest_TypeParam_> \
|
||||
void GTEST_TEST_CLASS_NAME_(CaseName, \
|
||||
TestName)<gtest_TypeParam_>::TestBody()
|
||||
|
||||
// Legacy API is deprecated but still available
|
||||
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
#define TYPED_TEST_CASE \
|
||||
static_assert(::testing::internal::TypedTestCaseIsDeprecated(), ""); \
|
||||
TYPED_TEST_SUITE
|
||||
#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
|
||||
// Implements type-parameterized tests.
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
|
||||
//
|
||||
// Expands to the namespace name that the type-parameterized tests for
|
||||
// the given type-parameterized test suite are defined in. The exact
|
||||
// name of the namespace is subject to change without notice.
|
||||
#define GTEST_SUITE_NAMESPACE_(TestSuiteName) gtest_suite_##TestSuiteName##_
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
|
||||
//
|
||||
// Expands to the name of the variable used to remember the names of
|
||||
// the defined tests in the given test suite.
|
||||
#define GTEST_TYPED_TEST_SUITE_P_STATE_(TestSuiteName) \
|
||||
gtest_typed_test_suite_p_state_##TestSuiteName##_
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE DIRECTLY.
|
||||
//
|
||||
// Expands to the name of the variable used to remember the names of
|
||||
// the registered tests in the given test suite.
|
||||
#define GTEST_REGISTERED_TEST_NAMES_(TestSuiteName) \
|
||||
gtest_registered_test_names_##TestSuiteName##_
|
||||
|
||||
// The variables defined in the type-parameterized test macros are
|
||||
// static as typically these macros are used in a .h file that can be
|
||||
// #included in multiple translation units linked together.
|
||||
#define TYPED_TEST_SUITE_P(SuiteName) \
|
||||
static ::testing::internal::TypedTestSuitePState \
|
||||
GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName)
|
||||
|
||||
// Legacy API is deprecated but still available
|
||||
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
#define TYPED_TEST_CASE_P \
|
||||
static_assert(::testing::internal::TypedTestCase_P_IsDeprecated(), ""); \
|
||||
TYPED_TEST_SUITE_P
|
||||
#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
|
||||
#define TYPED_TEST_P(SuiteName, TestName) \
|
||||
namespace GTEST_SUITE_NAMESPACE_(SuiteName) { \
|
||||
template <typename gtest_TypeParam_> \
|
||||
class TestName : public SuiteName<gtest_TypeParam_> { \
|
||||
private: \
|
||||
typedef SuiteName<gtest_TypeParam_> TestFixture; \
|
||||
typedef gtest_TypeParam_ TypeParam; \
|
||||
void TestBody() override; \
|
||||
}; \
|
||||
static bool gtest_##TestName##_defined_ GTEST_ATTRIBUTE_UNUSED_ = \
|
||||
GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).AddTestName( \
|
||||
__FILE__, __LINE__, GTEST_STRINGIFY_(SuiteName), \
|
||||
GTEST_STRINGIFY_(TestName)); \
|
||||
} \
|
||||
template <typename gtest_TypeParam_> \
|
||||
void GTEST_SUITE_NAMESPACE_( \
|
||||
SuiteName)::TestName<gtest_TypeParam_>::TestBody()
|
||||
|
||||
// Note: this won't work correctly if the trailing arguments are macros.
|
||||
#define REGISTER_TYPED_TEST_SUITE_P(SuiteName, ...) \
|
||||
namespace GTEST_SUITE_NAMESPACE_(SuiteName) { \
|
||||
typedef ::testing::internal::Templates<__VA_ARGS__> gtest_AllTests_; \
|
||||
} \
|
||||
static const char* const GTEST_REGISTERED_TEST_NAMES_( \
|
||||
SuiteName) GTEST_ATTRIBUTE_UNUSED_ = \
|
||||
GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).VerifyRegisteredTestNames( \
|
||||
GTEST_STRINGIFY_(SuiteName), __FILE__, __LINE__, #__VA_ARGS__)
|
||||
|
||||
// Legacy API is deprecated but still available
|
||||
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
#define REGISTER_TYPED_TEST_CASE_P \
|
||||
static_assert(::testing::internal::RegisterTypedTestCase_P_IsDeprecated(), \
|
||||
""); \
|
||||
REGISTER_TYPED_TEST_SUITE_P
|
||||
#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
|
||||
#define INSTANTIATE_TYPED_TEST_SUITE_P(Prefix, SuiteName, Types, ...) \
|
||||
static_assert(sizeof(GTEST_STRINGIFY_(Prefix)) > 1, \
|
||||
"test-suit-prefix must not be empty"); \
|
||||
static bool gtest_##Prefix##_##SuiteName GTEST_ATTRIBUTE_UNUSED_ = \
|
||||
::testing::internal::TypeParameterizedTestSuite< \
|
||||
SuiteName, GTEST_SUITE_NAMESPACE_(SuiteName)::gtest_AllTests_, \
|
||||
::testing::internal::GenerateTypeList<Types>::type>:: \
|
||||
Register(GTEST_STRINGIFY_(Prefix), \
|
||||
::testing::internal::CodeLocation(__FILE__, __LINE__), \
|
||||
>EST_TYPED_TEST_SUITE_P_STATE_(SuiteName), \
|
||||
GTEST_STRINGIFY_(SuiteName), \
|
||||
GTEST_REGISTERED_TEST_NAMES_(SuiteName), \
|
||||
::testing::internal::GenerateNames< \
|
||||
::testing::internal::NameGeneratorSelector< \
|
||||
__VA_ARGS__>::type, \
|
||||
::testing::internal::GenerateTypeList<Types>::type>())
|
||||
|
||||
// Legacy API is deprecated but still available
|
||||
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
#define INSTANTIATE_TYPED_TEST_CASE_P \
|
||||
static_assert( \
|
||||
::testing::internal::InstantiateTypedTestCase_P_IsDeprecated(), ""); \
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P
|
||||
#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,359 @@
|
||||
// Copyright 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// This file is AUTOMATICALLY GENERATED on 01/02/2019 by command
|
||||
// 'gen_gtest_pred_impl.py 5'. DO NOT EDIT BY HAND!
|
||||
//
|
||||
// Implements a family of generic predicate assertion macros.
|
||||
// GOOGLETEST_CM0001 DO NOT DELETE
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace testing {
|
||||
|
||||
// This header implements a family of generic predicate assertion
|
||||
// macros:
|
||||
//
|
||||
// ASSERT_PRED_FORMAT1(pred_format, v1)
|
||||
// ASSERT_PRED_FORMAT2(pred_format, v1, v2)
|
||||
// ...
|
||||
//
|
||||
// where pred_format is a function or functor that takes n (in the
|
||||
// case of ASSERT_PRED_FORMATn) values and their source expression
|
||||
// text, and returns a testing::AssertionResult. See the definition
|
||||
// of ASSERT_EQ in gtest.h for an example.
|
||||
//
|
||||
// If you don't care about formatting, you can use the more
|
||||
// restrictive version:
|
||||
//
|
||||
// ASSERT_PRED1(pred, v1)
|
||||
// ASSERT_PRED2(pred, v1, v2)
|
||||
// ...
|
||||
//
|
||||
// where pred is an n-ary function or functor that returns bool,
|
||||
// and the values v1, v2, ..., must support the << operator for
|
||||
// streaming to std::ostream.
|
||||
//
|
||||
// We also define the EXPECT_* variations.
|
||||
//
|
||||
// For now we only support predicates whose arity is at most 5.
|
||||
// Please email googletestframework@googlegroups.com if you need
|
||||
// support for higher arities.
|
||||
|
||||
// GTEST_ASSERT_ is the basic statement to which all of the assertions
|
||||
// in this file reduce. Don't use this in your code.
|
||||
|
||||
#define GTEST_ASSERT_(expression, on_failure) \
|
||||
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
|
||||
if (const ::testing::AssertionResult gtest_ar = (expression)) \
|
||||
; \
|
||||
else \
|
||||
on_failure(gtest_ar.failure_message())
|
||||
|
||||
|
||||
// Helper function for implementing {EXPECT|ASSERT}_PRED1. Don't use
|
||||
// this in your code.
|
||||
template <typename Pred,
|
||||
typename T1>
|
||||
AssertionResult AssertPred1Helper(const char* pred_text,
|
||||
const char* e1,
|
||||
Pred pred,
|
||||
const T1& v1) {
|
||||
if (pred(v1)) return AssertionSuccess();
|
||||
|
||||
return AssertionFailure()
|
||||
<< pred_text << "(" << e1 << ") evaluates to false, where"
|
||||
<< "\n"
|
||||
<< e1 << " evaluates to " << ::testing::PrintToString(v1);
|
||||
}
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT1.
|
||||
// Don't use this in your code.
|
||||
#define GTEST_PRED_FORMAT1_(pred_format, v1, on_failure)\
|
||||
GTEST_ASSERT_(pred_format(#v1, v1), \
|
||||
on_failure)
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED1. Don't use
|
||||
// this in your code.
|
||||
#define GTEST_PRED1_(pred, v1, on_failure)\
|
||||
GTEST_ASSERT_(::testing::AssertPred1Helper(#pred, \
|
||||
#v1, \
|
||||
pred, \
|
||||
v1), on_failure)
|
||||
|
||||
// Unary predicate assertion macros.
|
||||
#define EXPECT_PRED_FORMAT1(pred_format, v1) \
|
||||
GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_NONFATAL_FAILURE_)
|
||||
#define EXPECT_PRED1(pred, v1) \
|
||||
GTEST_PRED1_(pred, v1, GTEST_NONFATAL_FAILURE_)
|
||||
#define ASSERT_PRED_FORMAT1(pred_format, v1) \
|
||||
GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_FATAL_FAILURE_)
|
||||
#define ASSERT_PRED1(pred, v1) \
|
||||
GTEST_PRED1_(pred, v1, GTEST_FATAL_FAILURE_)
|
||||
|
||||
|
||||
|
||||
// Helper function for implementing {EXPECT|ASSERT}_PRED2. Don't use
|
||||
// this in your code.
|
||||
template <typename Pred,
|
||||
typename T1,
|
||||
typename T2>
|
||||
AssertionResult AssertPred2Helper(const char* pred_text,
|
||||
const char* e1,
|
||||
const char* e2,
|
||||
Pred pred,
|
||||
const T1& v1,
|
||||
const T2& v2) {
|
||||
if (pred(v1, v2)) return AssertionSuccess();
|
||||
|
||||
return AssertionFailure()
|
||||
<< pred_text << "(" << e1 << ", " << e2
|
||||
<< ") evaluates to false, where"
|
||||
<< "\n"
|
||||
<< e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n"
|
||||
<< e2 << " evaluates to " << ::testing::PrintToString(v2);
|
||||
}
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT2.
|
||||
// Don't use this in your code.
|
||||
#define GTEST_PRED_FORMAT2_(pred_format, v1, v2, on_failure)\
|
||||
GTEST_ASSERT_(pred_format(#v1, #v2, v1, v2), \
|
||||
on_failure)
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED2. Don't use
|
||||
// this in your code.
|
||||
#define GTEST_PRED2_(pred, v1, v2, on_failure)\
|
||||
GTEST_ASSERT_(::testing::AssertPred2Helper(#pred, \
|
||||
#v1, \
|
||||
#v2, \
|
||||
pred, \
|
||||
v1, \
|
||||
v2), on_failure)
|
||||
|
||||
// Binary predicate assertion macros.
|
||||
#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \
|
||||
GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_)
|
||||
#define EXPECT_PRED2(pred, v1, v2) \
|
||||
GTEST_PRED2_(pred, v1, v2, GTEST_NONFATAL_FAILURE_)
|
||||
#define ASSERT_PRED_FORMAT2(pred_format, v1, v2) \
|
||||
GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_FATAL_FAILURE_)
|
||||
#define ASSERT_PRED2(pred, v1, v2) \
|
||||
GTEST_PRED2_(pred, v1, v2, GTEST_FATAL_FAILURE_)
|
||||
|
||||
|
||||
|
||||
// Helper function for implementing {EXPECT|ASSERT}_PRED3. Don't use
|
||||
// this in your code.
|
||||
template <typename Pred,
|
||||
typename T1,
|
||||
typename T2,
|
||||
typename T3>
|
||||
AssertionResult AssertPred3Helper(const char* pred_text,
|
||||
const char* e1,
|
||||
const char* e2,
|
||||
const char* e3,
|
||||
Pred pred,
|
||||
const T1& v1,
|
||||
const T2& v2,
|
||||
const T3& v3) {
|
||||
if (pred(v1, v2, v3)) return AssertionSuccess();
|
||||
|
||||
return AssertionFailure()
|
||||
<< pred_text << "(" << e1 << ", " << e2 << ", " << e3
|
||||
<< ") evaluates to false, where"
|
||||
<< "\n"
|
||||
<< e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n"
|
||||
<< e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n"
|
||||
<< e3 << " evaluates to " << ::testing::PrintToString(v3);
|
||||
}
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT3.
|
||||
// Don't use this in your code.
|
||||
#define GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, on_failure)\
|
||||
GTEST_ASSERT_(pred_format(#v1, #v2, #v3, v1, v2, v3), \
|
||||
on_failure)
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED3. Don't use
|
||||
// this in your code.
|
||||
#define GTEST_PRED3_(pred, v1, v2, v3, on_failure)\
|
||||
GTEST_ASSERT_(::testing::AssertPred3Helper(#pred, \
|
||||
#v1, \
|
||||
#v2, \
|
||||
#v3, \
|
||||
pred, \
|
||||
v1, \
|
||||
v2, \
|
||||
v3), on_failure)
|
||||
|
||||
// Ternary predicate assertion macros.
|
||||
#define EXPECT_PRED_FORMAT3(pred_format, v1, v2, v3) \
|
||||
GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_NONFATAL_FAILURE_)
|
||||
#define EXPECT_PRED3(pred, v1, v2, v3) \
|
||||
GTEST_PRED3_(pred, v1, v2, v3, GTEST_NONFATAL_FAILURE_)
|
||||
#define ASSERT_PRED_FORMAT3(pred_format, v1, v2, v3) \
|
||||
GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_FATAL_FAILURE_)
|
||||
#define ASSERT_PRED3(pred, v1, v2, v3) \
|
||||
GTEST_PRED3_(pred, v1, v2, v3, GTEST_FATAL_FAILURE_)
|
||||
|
||||
|
||||
|
||||
// Helper function for implementing {EXPECT|ASSERT}_PRED4. Don't use
|
||||
// this in your code.
|
||||
template <typename Pred,
|
||||
typename T1,
|
||||
typename T2,
|
||||
typename T3,
|
||||
typename T4>
|
||||
AssertionResult AssertPred4Helper(const char* pred_text,
|
||||
const char* e1,
|
||||
const char* e2,
|
||||
const char* e3,
|
||||
const char* e4,
|
||||
Pred pred,
|
||||
const T1& v1,
|
||||
const T2& v2,
|
||||
const T3& v3,
|
||||
const T4& v4) {
|
||||
if (pred(v1, v2, v3, v4)) return AssertionSuccess();
|
||||
|
||||
return AssertionFailure()
|
||||
<< pred_text << "(" << e1 << ", " << e2 << ", " << e3 << ", " << e4
|
||||
<< ") evaluates to false, where"
|
||||
<< "\n"
|
||||
<< e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n"
|
||||
<< e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n"
|
||||
<< e3 << " evaluates to " << ::testing::PrintToString(v3) << "\n"
|
||||
<< e4 << " evaluates to " << ::testing::PrintToString(v4);
|
||||
}
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT4.
|
||||
// Don't use this in your code.
|
||||
#define GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, on_failure)\
|
||||
GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, v1, v2, v3, v4), \
|
||||
on_failure)
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED4. Don't use
|
||||
// this in your code.
|
||||
#define GTEST_PRED4_(pred, v1, v2, v3, v4, on_failure)\
|
||||
GTEST_ASSERT_(::testing::AssertPred4Helper(#pred, \
|
||||
#v1, \
|
||||
#v2, \
|
||||
#v3, \
|
||||
#v4, \
|
||||
pred, \
|
||||
v1, \
|
||||
v2, \
|
||||
v3, \
|
||||
v4), on_failure)
|
||||
|
||||
// 4-ary predicate assertion macros.
|
||||
#define EXPECT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \
|
||||
GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_)
|
||||
#define EXPECT_PRED4(pred, v1, v2, v3, v4) \
|
||||
GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_)
|
||||
#define ASSERT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \
|
||||
GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_FATAL_FAILURE_)
|
||||
#define ASSERT_PRED4(pred, v1, v2, v3, v4) \
|
||||
GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_FATAL_FAILURE_)
|
||||
|
||||
|
||||
|
||||
// Helper function for implementing {EXPECT|ASSERT}_PRED5. Don't use
|
||||
// this in your code.
|
||||
template <typename Pred,
|
||||
typename T1,
|
||||
typename T2,
|
||||
typename T3,
|
||||
typename T4,
|
||||
typename T5>
|
||||
AssertionResult AssertPred5Helper(const char* pred_text,
|
||||
const char* e1,
|
||||
const char* e2,
|
||||
const char* e3,
|
||||
const char* e4,
|
||||
const char* e5,
|
||||
Pred pred,
|
||||
const T1& v1,
|
||||
const T2& v2,
|
||||
const T3& v3,
|
||||
const T4& v4,
|
||||
const T5& v5) {
|
||||
if (pred(v1, v2, v3, v4, v5)) return AssertionSuccess();
|
||||
|
||||
return AssertionFailure()
|
||||
<< pred_text << "(" << e1 << ", " << e2 << ", " << e3 << ", " << e4
|
||||
<< ", " << e5 << ") evaluates to false, where"
|
||||
<< "\n"
|
||||
<< e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n"
|
||||
<< e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n"
|
||||
<< e3 << " evaluates to " << ::testing::PrintToString(v3) << "\n"
|
||||
<< e4 << " evaluates to " << ::testing::PrintToString(v4) << "\n"
|
||||
<< e5 << " evaluates to " << ::testing::PrintToString(v5);
|
||||
}
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT5.
|
||||
// Don't use this in your code.
|
||||
#define GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, on_failure)\
|
||||
GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, #v5, v1, v2, v3, v4, v5), \
|
||||
on_failure)
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED5. Don't use
|
||||
// this in your code.
|
||||
#define GTEST_PRED5_(pred, v1, v2, v3, v4, v5, on_failure)\
|
||||
GTEST_ASSERT_(::testing::AssertPred5Helper(#pred, \
|
||||
#v1, \
|
||||
#v2, \
|
||||
#v3, \
|
||||
#v4, \
|
||||
#v5, \
|
||||
pred, \
|
||||
v1, \
|
||||
v2, \
|
||||
v3, \
|
||||
v4, \
|
||||
v5), on_failure)
|
||||
|
||||
// 5-ary predicate assertion macros.
|
||||
#define EXPECT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \
|
||||
GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_)
|
||||
#define EXPECT_PRED5(pred, v1, v2, v3, v4, v5) \
|
||||
GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_)
|
||||
#define ASSERT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \
|
||||
GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_)
|
||||
#define ASSERT_PRED5(pred, v1, v2, v3, v4, v5) \
|
||||
GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_)
|
||||
|
||||
|
||||
|
||||
} // namespace testing
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// Google C++ Testing and Mocking Framework definitions useful in production code.
|
||||
// GOOGLETEST_CM0003 DO NOT DELETE
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_
|
||||
|
||||
// When you need to test the private or protected members of a class,
|
||||
// use the FRIEND_TEST macro to declare your tests as friends of the
|
||||
// class. For example:
|
||||
//
|
||||
// class MyClass {
|
||||
// private:
|
||||
// void PrivateMethod();
|
||||
// FRIEND_TEST(MyClassTest, PrivateMethodWorks);
|
||||
// };
|
||||
//
|
||||
// class MyClassTest : public testing::Test {
|
||||
// // ...
|
||||
// };
|
||||
//
|
||||
// TEST_F(MyClassTest, PrivateMethodWorks) {
|
||||
// // Can call MyClass::PrivateMethod() here.
|
||||
// }
|
||||
//
|
||||
// Note: The test class must be in the same namespace as the class being tested.
|
||||
// For example, putting MyClassTest in an anonymous namespace will not work.
|
||||
|
||||
#define FRIEND_TEST(test_case_name, test_name)\
|
||||
friend class test_case_name##_##test_name##_Test
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_
|
||||
@@ -0,0 +1,56 @@
|
||||
# Customization Points
|
||||
|
||||
The custom directory is an injection point for custom user configurations.
|
||||
|
||||
## Header `gtest.h`
|
||||
|
||||
### The following macros can be defined:
|
||||
|
||||
* `GTEST_OS_STACK_TRACE_GETTER_` - The name of an implementation of
|
||||
`OsStackTraceGetterInterface`.
|
||||
* `GTEST_CUSTOM_TEMPDIR_FUNCTION_` - An override for `testing::TempDir()`. See
|
||||
`testing::TempDir` for semantics and signature.
|
||||
|
||||
## Header `gtest-port.h`
|
||||
|
||||
The following macros can be defined:
|
||||
|
||||
### Flag related macros:
|
||||
|
||||
* `GTEST_FLAG(flag_name)`
|
||||
* `GTEST_USE_OWN_FLAGFILE_FLAG_` - Define to 0 when the system provides its
|
||||
own flagfile flag parsing.
|
||||
* `GTEST_DECLARE_bool_(name)`
|
||||
* `GTEST_DECLARE_int32_(name)`
|
||||
* `GTEST_DECLARE_string_(name)`
|
||||
* `GTEST_DEFINE_bool_(name, default_val, doc)`
|
||||
* `GTEST_DEFINE_int32_(name, default_val, doc)`
|
||||
* `GTEST_DEFINE_string_(name, default_val, doc)`
|
||||
|
||||
### Logging:
|
||||
|
||||
* `GTEST_LOG_(severity)`
|
||||
* `GTEST_CHECK_(condition)`
|
||||
* Functions `LogToStderr()` and `FlushInfoLog()` have to be provided too.
|
||||
|
||||
### Threading:
|
||||
|
||||
* `GTEST_HAS_NOTIFICATION_` - Enabled if Notification is already provided.
|
||||
* `GTEST_HAS_MUTEX_AND_THREAD_LOCAL_` - Enabled if `Mutex` and `ThreadLocal`
|
||||
are already provided. Must also provide `GTEST_DECLARE_STATIC_MUTEX_(mutex)`
|
||||
and `GTEST_DEFINE_STATIC_MUTEX_(mutex)`
|
||||
* `GTEST_EXCLUSIVE_LOCK_REQUIRED_(locks)`
|
||||
* `GTEST_LOCK_EXCLUDED_(locks)`
|
||||
|
||||
### Underlying library support features
|
||||
|
||||
* `GTEST_HAS_CXXABI_H_`
|
||||
|
||||
### Exporting API symbols:
|
||||
|
||||
* `GTEST_API_` - Specifier for exported symbols.
|
||||
|
||||
## Header `gtest-printers.h`
|
||||
|
||||
* See documentation at `gtest/gtest-printers.h` for details on how to define a
|
||||
custom printer.
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2015, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Injection point for custom user configurations. See README for details
|
||||
//
|
||||
// ** Custom implementation starts here **
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2015, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// This file provides an injection point for custom printers in a local
|
||||
// installation of gTest.
|
||||
// It will be included from gtest-printers.h and the overrides in this file
|
||||
// will be visible to everyone.
|
||||
//
|
||||
// Injection point for custom user configurations. See README for details
|
||||
//
|
||||
// ** Custom implementation starts here **
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2015, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Injection point for custom user configurations. See README for details
|
||||
//
|
||||
// ** Custom implementation starts here **
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_H_
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_H_
|
||||
@@ -0,0 +1,304 @@
|
||||
// Copyright 2005, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// The Google C++ Testing and Mocking Framework (Google Test)
|
||||
//
|
||||
// This header file defines internal utilities needed for implementing
|
||||
// death tests. They are subject to change without notice.
|
||||
// GOOGLETEST_CM0001 DO NOT DELETE
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_
|
||||
|
||||
#include "gtest/gtest-matchers.h"
|
||||
#include "gtest/internal/gtest-internal.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <memory>
|
||||
|
||||
namespace testing {
|
||||
namespace internal {
|
||||
|
||||
GTEST_DECLARE_string_(internal_run_death_test);
|
||||
|
||||
// Names of the flags (needed for parsing Google Test flags).
|
||||
const char kDeathTestStyleFlag[] = "death_test_style";
|
||||
const char kDeathTestUseFork[] = "death_test_use_fork";
|
||||
const char kInternalRunDeathTestFlag[] = "internal_run_death_test";
|
||||
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
|
||||
/* class A needs to have dll-interface to be used by clients of class B */)
|
||||
|
||||
// DeathTest is a class that hides much of the complexity of the
|
||||
// GTEST_DEATH_TEST_ macro. It is abstract; its static Create method
|
||||
// returns a concrete class that depends on the prevailing death test
|
||||
// style, as defined by the --gtest_death_test_style and/or
|
||||
// --gtest_internal_run_death_test flags.
|
||||
|
||||
// In describing the results of death tests, these terms are used with
|
||||
// the corresponding definitions:
|
||||
//
|
||||
// exit status: The integer exit information in the format specified
|
||||
// by wait(2)
|
||||
// exit code: The integer code passed to exit(3), _exit(2), or
|
||||
// returned from main()
|
||||
class GTEST_API_ DeathTest {
|
||||
public:
|
||||
// Create returns false if there was an error determining the
|
||||
// appropriate action to take for the current death test; for example,
|
||||
// if the gtest_death_test_style flag is set to an invalid value.
|
||||
// The LastMessage method will return a more detailed message in that
|
||||
// case. Otherwise, the DeathTest pointer pointed to by the "test"
|
||||
// argument is set. If the death test should be skipped, the pointer
|
||||
// is set to NULL; otherwise, it is set to the address of a new concrete
|
||||
// DeathTest object that controls the execution of the current test.
|
||||
static bool Create(const char* statement, Matcher<const std::string&> matcher,
|
||||
const char* file, int line, DeathTest** test);
|
||||
DeathTest();
|
||||
virtual ~DeathTest() { }
|
||||
|
||||
// A helper class that aborts a death test when it's deleted.
|
||||
class ReturnSentinel {
|
||||
public:
|
||||
explicit ReturnSentinel(DeathTest* test) : test_(test) { }
|
||||
~ReturnSentinel() { test_->Abort(TEST_ENCOUNTERED_RETURN_STATEMENT); }
|
||||
private:
|
||||
DeathTest* const test_;
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(ReturnSentinel);
|
||||
} GTEST_ATTRIBUTE_UNUSED_;
|
||||
|
||||
// An enumeration of possible roles that may be taken when a death
|
||||
// test is encountered. EXECUTE means that the death test logic should
|
||||
// be executed immediately. OVERSEE means that the program should prepare
|
||||
// the appropriate environment for a child process to execute the death
|
||||
// test, then wait for it to complete.
|
||||
enum TestRole { OVERSEE_TEST, EXECUTE_TEST };
|
||||
|
||||
// An enumeration of the three reasons that a test might be aborted.
|
||||
enum AbortReason {
|
||||
TEST_ENCOUNTERED_RETURN_STATEMENT,
|
||||
TEST_THREW_EXCEPTION,
|
||||
TEST_DID_NOT_DIE
|
||||
};
|
||||
|
||||
// Assumes one of the above roles.
|
||||
virtual TestRole AssumeRole() = 0;
|
||||
|
||||
// Waits for the death test to finish and returns its status.
|
||||
virtual int Wait() = 0;
|
||||
|
||||
// Returns true if the death test passed; that is, the test process
|
||||
// exited during the test, its exit status matches a user-supplied
|
||||
// predicate, and its stderr output matches a user-supplied regular
|
||||
// expression.
|
||||
// The user-supplied predicate may be a macro expression rather
|
||||
// than a function pointer or functor, or else Wait and Passed could
|
||||
// be combined.
|
||||
virtual bool Passed(bool exit_status_ok) = 0;
|
||||
|
||||
// Signals that the death test did not die as expected.
|
||||
virtual void Abort(AbortReason reason) = 0;
|
||||
|
||||
// Returns a human-readable outcome message regarding the outcome of
|
||||
// the last death test.
|
||||
static const char* LastMessage();
|
||||
|
||||
static void set_last_death_test_message(const std::string& message);
|
||||
|
||||
private:
|
||||
// A string containing a description of the outcome of the last death test.
|
||||
static std::string last_death_test_message_;
|
||||
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(DeathTest);
|
||||
};
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
|
||||
|
||||
// Factory interface for death tests. May be mocked out for testing.
|
||||
class DeathTestFactory {
|
||||
public:
|
||||
virtual ~DeathTestFactory() { }
|
||||
virtual bool Create(const char* statement,
|
||||
Matcher<const std::string&> matcher, const char* file,
|
||||
int line, DeathTest** test) = 0;
|
||||
};
|
||||
|
||||
// A concrete DeathTestFactory implementation for normal use.
|
||||
class DefaultDeathTestFactory : public DeathTestFactory {
|
||||
public:
|
||||
bool Create(const char* statement, Matcher<const std::string&> matcher,
|
||||
const char* file, int line, DeathTest** test) override;
|
||||
};
|
||||
|
||||
// Returns true if exit_status describes a process that was terminated
|
||||
// by a signal, or exited normally with a nonzero exit code.
|
||||
GTEST_API_ bool ExitedUnsuccessfully(int exit_status);
|
||||
|
||||
// A string passed to EXPECT_DEATH (etc.) is caught by one of these overloads
|
||||
// and interpreted as a regex (rather than an Eq matcher) for legacy
|
||||
// compatibility.
|
||||
inline Matcher<const ::std::string&> MakeDeathTestMatcher(
|
||||
::testing::internal::RE regex) {
|
||||
return ContainsRegex(regex.pattern());
|
||||
}
|
||||
inline Matcher<const ::std::string&> MakeDeathTestMatcher(const char* regex) {
|
||||
return ContainsRegex(regex);
|
||||
}
|
||||
inline Matcher<const ::std::string&> MakeDeathTestMatcher(
|
||||
const ::std::string& regex) {
|
||||
return ContainsRegex(regex);
|
||||
}
|
||||
|
||||
// If a Matcher<const ::std::string&> is passed to EXPECT_DEATH (etc.), it's
|
||||
// used directly.
|
||||
inline Matcher<const ::std::string&> MakeDeathTestMatcher(
|
||||
Matcher<const ::std::string&> matcher) {
|
||||
return matcher;
|
||||
}
|
||||
|
||||
// Traps C++ exceptions escaping statement and reports them as test
|
||||
// failures. Note that trapping SEH exceptions is not implemented here.
|
||||
# if GTEST_HAS_EXCEPTIONS
|
||||
# define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \
|
||||
try { \
|
||||
GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
|
||||
} catch (const ::std::exception& gtest_exception) { \
|
||||
fprintf(\
|
||||
stderr, \
|
||||
"\n%s: Caught std::exception-derived exception escaping the " \
|
||||
"death test statement. Exception message: %s\n", \
|
||||
::testing::internal::FormatFileLocation(__FILE__, __LINE__).c_str(), \
|
||||
gtest_exception.what()); \
|
||||
fflush(stderr); \
|
||||
death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \
|
||||
} catch (...) { \
|
||||
death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \
|
||||
}
|
||||
|
||||
# else
|
||||
# define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \
|
||||
GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement)
|
||||
|
||||
# endif
|
||||
|
||||
// This macro is for implementing ASSERT_DEATH*, EXPECT_DEATH*,
|
||||
// ASSERT_EXIT*, and EXPECT_EXIT*.
|
||||
#define GTEST_DEATH_TEST_(statement, predicate, regex_or_matcher, fail) \
|
||||
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
|
||||
if (::testing::internal::AlwaysTrue()) { \
|
||||
::testing::internal::DeathTest* gtest_dt; \
|
||||
if (!::testing::internal::DeathTest::Create( \
|
||||
#statement, \
|
||||
::testing::internal::MakeDeathTestMatcher(regex_or_matcher), \
|
||||
__FILE__, __LINE__, >est_dt)) { \
|
||||
goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \
|
||||
} \
|
||||
if (gtest_dt != nullptr) { \
|
||||
std::unique_ptr< ::testing::internal::DeathTest> gtest_dt_ptr(gtest_dt); \
|
||||
switch (gtest_dt->AssumeRole()) { \
|
||||
case ::testing::internal::DeathTest::OVERSEE_TEST: \
|
||||
if (!gtest_dt->Passed(predicate(gtest_dt->Wait()))) { \
|
||||
goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \
|
||||
} \
|
||||
break; \
|
||||
case ::testing::internal::DeathTest::EXECUTE_TEST: { \
|
||||
::testing::internal::DeathTest::ReturnSentinel gtest_sentinel( \
|
||||
gtest_dt); \
|
||||
GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, gtest_dt); \
|
||||
gtest_dt->Abort(::testing::internal::DeathTest::TEST_DID_NOT_DIE); \
|
||||
break; \
|
||||
} \
|
||||
default: \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
} else \
|
||||
GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__) \
|
||||
: fail(::testing::internal::DeathTest::LastMessage())
|
||||
// The symbol "fail" here expands to something into which a message
|
||||
// can be streamed.
|
||||
|
||||
// This macro is for implementing ASSERT/EXPECT_DEBUG_DEATH when compiled in
|
||||
// NDEBUG mode. In this case we need the statements to be executed and the macro
|
||||
// must accept a streamed message even though the message is never printed.
|
||||
// The regex object is not evaluated, but it is used to prevent "unused"
|
||||
// warnings and to avoid an expression that doesn't compile in debug mode.
|
||||
#define GTEST_EXECUTE_STATEMENT_(statement, regex_or_matcher) \
|
||||
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
|
||||
if (::testing::internal::AlwaysTrue()) { \
|
||||
GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
|
||||
} else if (!::testing::internal::AlwaysTrue()) { \
|
||||
::testing::internal::MakeDeathTestMatcher(regex_or_matcher); \
|
||||
} else \
|
||||
::testing::Message()
|
||||
|
||||
// A class representing the parsed contents of the
|
||||
// --gtest_internal_run_death_test flag, as it existed when
|
||||
// RUN_ALL_TESTS was called.
|
||||
class InternalRunDeathTestFlag {
|
||||
public:
|
||||
InternalRunDeathTestFlag(const std::string& a_file,
|
||||
int a_line,
|
||||
int an_index,
|
||||
int a_write_fd)
|
||||
: file_(a_file), line_(a_line), index_(an_index),
|
||||
write_fd_(a_write_fd) {}
|
||||
|
||||
~InternalRunDeathTestFlag() {
|
||||
if (write_fd_ >= 0)
|
||||
posix::Close(write_fd_);
|
||||
}
|
||||
|
||||
const std::string& file() const { return file_; }
|
||||
int line() const { return line_; }
|
||||
int index() const { return index_; }
|
||||
int write_fd() const { return write_fd_; }
|
||||
|
||||
private:
|
||||
std::string file_;
|
||||
int line_;
|
||||
int index_;
|
||||
int write_fd_;
|
||||
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(InternalRunDeathTestFlag);
|
||||
};
|
||||
|
||||
// Returns a newly created InternalRunDeathTestFlag object with fields
|
||||
// initialized from the GTEST_FLAG(internal_run_death_test) flag if
|
||||
// the flag is specified; otherwise returns NULL.
|
||||
InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag();
|
||||
|
||||
#endif // GTEST_HAS_DEATH_TEST
|
||||
|
||||
} // namespace internal
|
||||
} // namespace testing
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_
|
||||
@@ -0,0 +1,211 @@
|
||||
// Copyright 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Google Test filepath utilities
|
||||
//
|
||||
// This header file declares classes and functions used internally by
|
||||
// Google Test. They are subject to change without notice.
|
||||
//
|
||||
// This file is #included in gtest/internal/gtest-internal.h.
|
||||
// Do not include this header file separately!
|
||||
|
||||
// GOOGLETEST_CM0001 DO NOT DELETE
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_
|
||||
|
||||
#include "gtest/internal/gtest-string.h"
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
|
||||
/* class A needs to have dll-interface to be used by clients of class B */)
|
||||
|
||||
namespace testing {
|
||||
namespace internal {
|
||||
|
||||
// FilePath - a class for file and directory pathname manipulation which
|
||||
// handles platform-specific conventions (like the pathname separator).
|
||||
// Used for helper functions for naming files in a directory for xml output.
|
||||
// Except for Set methods, all methods are const or static, which provides an
|
||||
// "immutable value object" -- useful for peace of mind.
|
||||
// A FilePath with a value ending in a path separator ("like/this/") represents
|
||||
// a directory, otherwise it is assumed to represent a file. In either case,
|
||||
// it may or may not represent an actual file or directory in the file system.
|
||||
// Names are NOT checked for syntax correctness -- no checking for illegal
|
||||
// characters, malformed paths, etc.
|
||||
|
||||
class GTEST_API_ FilePath {
|
||||
public:
|
||||
FilePath() : pathname_("") { }
|
||||
FilePath(const FilePath& rhs) : pathname_(rhs.pathname_) { }
|
||||
|
||||
explicit FilePath(const std::string& pathname) : pathname_(pathname) {
|
||||
Normalize();
|
||||
}
|
||||
|
||||
FilePath& operator=(const FilePath& rhs) {
|
||||
Set(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Set(const FilePath& rhs) {
|
||||
pathname_ = rhs.pathname_;
|
||||
}
|
||||
|
||||
const std::string& string() const { return pathname_; }
|
||||
const char* c_str() const { return pathname_.c_str(); }
|
||||
|
||||
// Returns the current working directory, or "" if unsuccessful.
|
||||
static FilePath GetCurrentDir();
|
||||
|
||||
// Given directory = "dir", base_name = "test", number = 0,
|
||||
// extension = "xml", returns "dir/test.xml". If number is greater
|
||||
// than zero (e.g., 12), returns "dir/test_12.xml".
|
||||
// On Windows platform, uses \ as the separator rather than /.
|
||||
static FilePath MakeFileName(const FilePath& directory,
|
||||
const FilePath& base_name,
|
||||
int number,
|
||||
const char* extension);
|
||||
|
||||
// Given directory = "dir", relative_path = "test.xml",
|
||||
// returns "dir/test.xml".
|
||||
// On Windows, uses \ as the separator rather than /.
|
||||
static FilePath ConcatPaths(const FilePath& directory,
|
||||
const FilePath& relative_path);
|
||||
|
||||
// Returns a pathname for a file that does not currently exist. The pathname
|
||||
// will be directory/base_name.extension or
|
||||
// directory/base_name_<number>.extension if directory/base_name.extension
|
||||
// already exists. The number will be incremented until a pathname is found
|
||||
// that does not already exist.
|
||||
// Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'.
|
||||
// There could be a race condition if two or more processes are calling this
|
||||
// function at the same time -- they could both pick the same filename.
|
||||
static FilePath GenerateUniqueFileName(const FilePath& directory,
|
||||
const FilePath& base_name,
|
||||
const char* extension);
|
||||
|
||||
// Returns true if and only if the path is "".
|
||||
bool IsEmpty() const { return pathname_.empty(); }
|
||||
|
||||
// If input name has a trailing separator character, removes it and returns
|
||||
// the name, otherwise return the name string unmodified.
|
||||
// On Windows platform, uses \ as the separator, other platforms use /.
|
||||
FilePath RemoveTrailingPathSeparator() const;
|
||||
|
||||
// Returns a copy of the FilePath with the directory part removed.
|
||||
// Example: FilePath("path/to/file").RemoveDirectoryName() returns
|
||||
// FilePath("file"). If there is no directory part ("just_a_file"), it returns
|
||||
// the FilePath unmodified. If there is no file part ("just_a_dir/") it
|
||||
// returns an empty FilePath ("").
|
||||
// On Windows platform, '\' is the path separator, otherwise it is '/'.
|
||||
FilePath RemoveDirectoryName() const;
|
||||
|
||||
// RemoveFileName returns the directory path with the filename removed.
|
||||
// Example: FilePath("path/to/file").RemoveFileName() returns "path/to/".
|
||||
// If the FilePath is "a_file" or "/a_file", RemoveFileName returns
|
||||
// FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does
|
||||
// not have a file, like "just/a/dir/", it returns the FilePath unmodified.
|
||||
// On Windows platform, '\' is the path separator, otherwise it is '/'.
|
||||
FilePath RemoveFileName() const;
|
||||
|
||||
// Returns a copy of the FilePath with the case-insensitive extension removed.
|
||||
// Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns
|
||||
// FilePath("dir/file"). If a case-insensitive extension is not
|
||||
// found, returns a copy of the original FilePath.
|
||||
FilePath RemoveExtension(const char* extension) const;
|
||||
|
||||
// Creates directories so that path exists. Returns true if successful or if
|
||||
// the directories already exist; returns false if unable to create
|
||||
// directories for any reason. Will also return false if the FilePath does
|
||||
// not represent a directory (that is, it doesn't end with a path separator).
|
||||
bool CreateDirectoriesRecursively() const;
|
||||
|
||||
// Create the directory so that path exists. Returns true if successful or
|
||||
// if the directory already exists; returns false if unable to create the
|
||||
// directory for any reason, including if the parent directory does not
|
||||
// exist. Not named "CreateDirectory" because that's a macro on Windows.
|
||||
bool CreateFolder() const;
|
||||
|
||||
// Returns true if FilePath describes something in the file-system,
|
||||
// either a file, directory, or whatever, and that something exists.
|
||||
bool FileOrDirectoryExists() const;
|
||||
|
||||
// Returns true if pathname describes a directory in the file-system
|
||||
// that exists.
|
||||
bool DirectoryExists() const;
|
||||
|
||||
// Returns true if FilePath ends with a path separator, which indicates that
|
||||
// it is intended to represent a directory. Returns false otherwise.
|
||||
// This does NOT check that a directory (or file) actually exists.
|
||||
bool IsDirectory() const;
|
||||
|
||||
// Returns true if pathname describes a root directory. (Windows has one
|
||||
// root directory per disk drive.)
|
||||
bool IsRootDirectory() const;
|
||||
|
||||
// Returns true if pathname describes an absolute path.
|
||||
bool IsAbsolutePath() const;
|
||||
|
||||
private:
|
||||
// Replaces multiple consecutive separators with a single separator.
|
||||
// For example, "bar///foo" becomes "bar/foo". Does not eliminate other
|
||||
// redundancies that might be in a pathname involving "." or "..".
|
||||
//
|
||||
// A pathname with multiple consecutive separators may occur either through
|
||||
// user error or as a result of some scripts or APIs that generate a pathname
|
||||
// with a trailing separator. On other platforms the same API or script
|
||||
// may NOT generate a pathname with a trailing "/". Then elsewhere that
|
||||
// pathname may have another "/" and pathname components added to it,
|
||||
// without checking for the separator already being there.
|
||||
// The script language and operating system may allow paths like "foo//bar"
|
||||
// but some of the functions in FilePath will not handle that correctly. In
|
||||
// particular, RemoveTrailingPathSeparator() only removes one separator, and
|
||||
// it is called in CreateDirectoriesRecursively() assuming that it will change
|
||||
// a pathname from directory syntax (trailing separator) to filename syntax.
|
||||
//
|
||||
// On Windows this method also replaces the alternate path separator '/' with
|
||||
// the primary path separator '\\', so that for example "bar\\/\\foo" becomes
|
||||
// "bar\\foo".
|
||||
|
||||
void Normalize();
|
||||
|
||||
// Returns a pointer to the last occurrence of a valid path separator in
|
||||
// the FilePath. On Windows, for example, both '/' and '\' are valid path
|
||||
// separators. Returns NULL if no path separator was found.
|
||||
const char* FindLastPathSeparator() const;
|
||||
|
||||
std::string pathname_;
|
||||
}; // class FilePath
|
||||
|
||||
} // namespace internal
|
||||
} // namespace testing
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,947 @@
|
||||
// Copyright 2008 Google Inc.
|
||||
// All Rights Reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
// Type and function utilities for implementing parameterized tests.
|
||||
|
||||
// GOOGLETEST_CM0001 DO NOT DELETE
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/internal/gtest-internal.h"
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
#include "gtest/gtest-printers.h"
|
||||
#include "gtest/gtest-test-part.h"
|
||||
|
||||
namespace testing {
|
||||
// Input to a parameterized test name generator, describing a test parameter.
|
||||
// Consists of the parameter value and the integer parameter index.
|
||||
template <class ParamType>
|
||||
struct TestParamInfo {
|
||||
TestParamInfo(const ParamType& a_param, size_t an_index) :
|
||||
param(a_param),
|
||||
index(an_index) {}
|
||||
ParamType param;
|
||||
size_t index;
|
||||
};
|
||||
|
||||
// A builtin parameterized test name generator which returns the result of
|
||||
// testing::PrintToString.
|
||||
struct PrintToStringParamName {
|
||||
template <class ParamType>
|
||||
std::string operator()(const TestParamInfo<ParamType>& info) const {
|
||||
return PrintToString(info.param);
|
||||
}
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
|
||||
// Utility Functions
|
||||
|
||||
// Outputs a message explaining invalid registration of different
|
||||
// fixture class for the same test suite. This may happen when
|
||||
// TEST_P macro is used to define two tests with the same name
|
||||
// but in different namespaces.
|
||||
GTEST_API_ void ReportInvalidTestSuiteType(const char* test_suite_name,
|
||||
CodeLocation code_location);
|
||||
|
||||
template <typename> class ParamGeneratorInterface;
|
||||
template <typename> class ParamGenerator;
|
||||
|
||||
// Interface for iterating over elements provided by an implementation
|
||||
// of ParamGeneratorInterface<T>.
|
||||
template <typename T>
|
||||
class ParamIteratorInterface {
|
||||
public:
|
||||
virtual ~ParamIteratorInterface() {}
|
||||
// A pointer to the base generator instance.
|
||||
// Used only for the purposes of iterator comparison
|
||||
// to make sure that two iterators belong to the same generator.
|
||||
virtual const ParamGeneratorInterface<T>* BaseGenerator() const = 0;
|
||||
// Advances iterator to point to the next element
|
||||
// provided by the generator. The caller is responsible
|
||||
// for not calling Advance() on an iterator equal to
|
||||
// BaseGenerator()->End().
|
||||
virtual void Advance() = 0;
|
||||
// Clones the iterator object. Used for implementing copy semantics
|
||||
// of ParamIterator<T>.
|
||||
virtual ParamIteratorInterface* Clone() const = 0;
|
||||
// Dereferences the current iterator and provides (read-only) access
|
||||
// to the pointed value. It is the caller's responsibility not to call
|
||||
// Current() on an iterator equal to BaseGenerator()->End().
|
||||
// Used for implementing ParamGenerator<T>::operator*().
|
||||
virtual const T* Current() const = 0;
|
||||
// Determines whether the given iterator and other point to the same
|
||||
// element in the sequence generated by the generator.
|
||||
// Used for implementing ParamGenerator<T>::operator==().
|
||||
virtual bool Equals(const ParamIteratorInterface& other) const = 0;
|
||||
};
|
||||
|
||||
// Class iterating over elements provided by an implementation of
|
||||
// ParamGeneratorInterface<T>. It wraps ParamIteratorInterface<T>
|
||||
// and implements the const forward iterator concept.
|
||||
template <typename T>
|
||||
class ParamIterator {
|
||||
public:
|
||||
typedef T value_type;
|
||||
typedef const T& reference;
|
||||
typedef ptrdiff_t difference_type;
|
||||
|
||||
// ParamIterator assumes ownership of the impl_ pointer.
|
||||
ParamIterator(const ParamIterator& other) : impl_(other.impl_->Clone()) {}
|
||||
ParamIterator& operator=(const ParamIterator& other) {
|
||||
if (this != &other)
|
||||
impl_.reset(other.impl_->Clone());
|
||||
return *this;
|
||||
}
|
||||
|
||||
const T& operator*() const { return *impl_->Current(); }
|
||||
const T* operator->() const { return impl_->Current(); }
|
||||
// Prefix version of operator++.
|
||||
ParamIterator& operator++() {
|
||||
impl_->Advance();
|
||||
return *this;
|
||||
}
|
||||
// Postfix version of operator++.
|
||||
ParamIterator operator++(int /*unused*/) {
|
||||
ParamIteratorInterface<T>* clone = impl_->Clone();
|
||||
impl_->Advance();
|
||||
return ParamIterator(clone);
|
||||
}
|
||||
bool operator==(const ParamIterator& other) const {
|
||||
return impl_.get() == other.impl_.get() || impl_->Equals(*other.impl_);
|
||||
}
|
||||
bool operator!=(const ParamIterator& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ParamGenerator<T>;
|
||||
explicit ParamIterator(ParamIteratorInterface<T>* impl) : impl_(impl) {}
|
||||
std::unique_ptr<ParamIteratorInterface<T> > impl_;
|
||||
};
|
||||
|
||||
// ParamGeneratorInterface<T> is the binary interface to access generators
|
||||
// defined in other translation units.
|
||||
template <typename T>
|
||||
class ParamGeneratorInterface {
|
||||
public:
|
||||
typedef T ParamType;
|
||||
|
||||
virtual ~ParamGeneratorInterface() {}
|
||||
|
||||
// Generator interface definition
|
||||
virtual ParamIteratorInterface<T>* Begin() const = 0;
|
||||
virtual ParamIteratorInterface<T>* End() const = 0;
|
||||
};
|
||||
|
||||
// Wraps ParamGeneratorInterface<T> and provides general generator syntax
|
||||
// compatible with the STL Container concept.
|
||||
// This class implements copy initialization semantics and the contained
|
||||
// ParamGeneratorInterface<T> instance is shared among all copies
|
||||
// of the original object. This is possible because that instance is immutable.
|
||||
template<typename T>
|
||||
class ParamGenerator {
|
||||
public:
|
||||
typedef ParamIterator<T> iterator;
|
||||
|
||||
explicit ParamGenerator(ParamGeneratorInterface<T>* impl) : impl_(impl) {}
|
||||
ParamGenerator(const ParamGenerator& other) : impl_(other.impl_) {}
|
||||
|
||||
ParamGenerator& operator=(const ParamGenerator& other) {
|
||||
impl_ = other.impl_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator begin() const { return iterator(impl_->Begin()); }
|
||||
iterator end() const { return iterator(impl_->End()); }
|
||||
|
||||
private:
|
||||
std::shared_ptr<const ParamGeneratorInterface<T> > impl_;
|
||||
};
|
||||
|
||||
// Generates values from a range of two comparable values. Can be used to
|
||||
// generate sequences of user-defined types that implement operator+() and
|
||||
// operator<().
|
||||
// This class is used in the Range() function.
|
||||
template <typename T, typename IncrementT>
|
||||
class RangeGenerator : public ParamGeneratorInterface<T> {
|
||||
public:
|
||||
RangeGenerator(T begin, T end, IncrementT step)
|
||||
: begin_(begin), end_(end),
|
||||
step_(step), end_index_(CalculateEndIndex(begin, end, step)) {}
|
||||
~RangeGenerator() override {}
|
||||
|
||||
ParamIteratorInterface<T>* Begin() const override {
|
||||
return new Iterator(this, begin_, 0, step_);
|
||||
}
|
||||
ParamIteratorInterface<T>* End() const override {
|
||||
return new Iterator(this, end_, end_index_, step_);
|
||||
}
|
||||
|
||||
private:
|
||||
class Iterator : public ParamIteratorInterface<T> {
|
||||
public:
|
||||
Iterator(const ParamGeneratorInterface<T>* base, T value, int index,
|
||||
IncrementT step)
|
||||
: base_(base), value_(value), index_(index), step_(step) {}
|
||||
~Iterator() override {}
|
||||
|
||||
const ParamGeneratorInterface<T>* BaseGenerator() const override {
|
||||
return base_;
|
||||
}
|
||||
void Advance() override {
|
||||
value_ = static_cast<T>(value_ + step_);
|
||||
index_++;
|
||||
}
|
||||
ParamIteratorInterface<T>* Clone() const override {
|
||||
return new Iterator(*this);
|
||||
}
|
||||
const T* Current() const override { return &value_; }
|
||||
bool Equals(const ParamIteratorInterface<T>& other) const override {
|
||||
// Having the same base generator guarantees that the other
|
||||
// iterator is of the same type and we can downcast.
|
||||
GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
|
||||
<< "The program attempted to compare iterators "
|
||||
<< "from different generators." << std::endl;
|
||||
const int other_index =
|
||||
CheckedDowncastToActualType<const Iterator>(&other)->index_;
|
||||
return index_ == other_index;
|
||||
}
|
||||
|
||||
private:
|
||||
Iterator(const Iterator& other)
|
||||
: ParamIteratorInterface<T>(),
|
||||
base_(other.base_), value_(other.value_), index_(other.index_),
|
||||
step_(other.step_) {}
|
||||
|
||||
// No implementation - assignment is unsupported.
|
||||
void operator=(const Iterator& other);
|
||||
|
||||
const ParamGeneratorInterface<T>* const base_;
|
||||
T value_;
|
||||
int index_;
|
||||
const IncrementT step_;
|
||||
}; // class RangeGenerator::Iterator
|
||||
|
||||
static int CalculateEndIndex(const T& begin,
|
||||
const T& end,
|
||||
const IncrementT& step) {
|
||||
int end_index = 0;
|
||||
for (T i = begin; i < end; i = static_cast<T>(i + step))
|
||||
end_index++;
|
||||
return end_index;
|
||||
}
|
||||
|
||||
// No implementation - assignment is unsupported.
|
||||
void operator=(const RangeGenerator& other);
|
||||
|
||||
const T begin_;
|
||||
const T end_;
|
||||
const IncrementT step_;
|
||||
// The index for the end() iterator. All the elements in the generated
|
||||
// sequence are indexed (0-based) to aid iterator comparison.
|
||||
const int end_index_;
|
||||
}; // class RangeGenerator
|
||||
|
||||
|
||||
// Generates values from a pair of STL-style iterators. Used in the
|
||||
// ValuesIn() function. The elements are copied from the source range
|
||||
// since the source can be located on the stack, and the generator
|
||||
// is likely to persist beyond that stack frame.
|
||||
template <typename T>
|
||||
class ValuesInIteratorRangeGenerator : public ParamGeneratorInterface<T> {
|
||||
public:
|
||||
template <typename ForwardIterator>
|
||||
ValuesInIteratorRangeGenerator(ForwardIterator begin, ForwardIterator end)
|
||||
: container_(begin, end) {}
|
||||
~ValuesInIteratorRangeGenerator() override {}
|
||||
|
||||
ParamIteratorInterface<T>* Begin() const override {
|
||||
return new Iterator(this, container_.begin());
|
||||
}
|
||||
ParamIteratorInterface<T>* End() const override {
|
||||
return new Iterator(this, container_.end());
|
||||
}
|
||||
|
||||
private:
|
||||
typedef typename ::std::vector<T> ContainerType;
|
||||
|
||||
class Iterator : public ParamIteratorInterface<T> {
|
||||
public:
|
||||
Iterator(const ParamGeneratorInterface<T>* base,
|
||||
typename ContainerType::const_iterator iterator)
|
||||
: base_(base), iterator_(iterator) {}
|
||||
~Iterator() override {}
|
||||
|
||||
const ParamGeneratorInterface<T>* BaseGenerator() const override {
|
||||
return base_;
|
||||
}
|
||||
void Advance() override {
|
||||
++iterator_;
|
||||
value_.reset();
|
||||
}
|
||||
ParamIteratorInterface<T>* Clone() const override {
|
||||
return new Iterator(*this);
|
||||
}
|
||||
// We need to use cached value referenced by iterator_ because *iterator_
|
||||
// can return a temporary object (and of type other then T), so just
|
||||
// having "return &*iterator_;" doesn't work.
|
||||
// value_ is updated here and not in Advance() because Advance()
|
||||
// can advance iterator_ beyond the end of the range, and we cannot
|
||||
// detect that fact. The client code, on the other hand, is
|
||||
// responsible for not calling Current() on an out-of-range iterator.
|
||||
const T* Current() const override {
|
||||
if (value_.get() == nullptr) value_.reset(new T(*iterator_));
|
||||
return value_.get();
|
||||
}
|
||||
bool Equals(const ParamIteratorInterface<T>& other) const override {
|
||||
// Having the same base generator guarantees that the other
|
||||
// iterator is of the same type and we can downcast.
|
||||
GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
|
||||
<< "The program attempted to compare iterators "
|
||||
<< "from different generators." << std::endl;
|
||||
return iterator_ ==
|
||||
CheckedDowncastToActualType<const Iterator>(&other)->iterator_;
|
||||
}
|
||||
|
||||
private:
|
||||
Iterator(const Iterator& other)
|
||||
// The explicit constructor call suppresses a false warning
|
||||
// emitted by gcc when supplied with the -Wextra option.
|
||||
: ParamIteratorInterface<T>(),
|
||||
base_(other.base_),
|
||||
iterator_(other.iterator_) {}
|
||||
|
||||
const ParamGeneratorInterface<T>* const base_;
|
||||
typename ContainerType::const_iterator iterator_;
|
||||
// A cached value of *iterator_. We keep it here to allow access by
|
||||
// pointer in the wrapping iterator's operator->().
|
||||
// value_ needs to be mutable to be accessed in Current().
|
||||
// Use of std::unique_ptr helps manage cached value's lifetime,
|
||||
// which is bound by the lifespan of the iterator itself.
|
||||
mutable std::unique_ptr<const T> value_;
|
||||
}; // class ValuesInIteratorRangeGenerator::Iterator
|
||||
|
||||
// No implementation - assignment is unsupported.
|
||||
void operator=(const ValuesInIteratorRangeGenerator& other);
|
||||
|
||||
const ContainerType container_;
|
||||
}; // class ValuesInIteratorRangeGenerator
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
|
||||
//
|
||||
// Default parameterized test name generator, returns a string containing the
|
||||
// integer test parameter index.
|
||||
template <class ParamType>
|
||||
std::string DefaultParamName(const TestParamInfo<ParamType>& info) {
|
||||
Message name_stream;
|
||||
name_stream << info.index;
|
||||
return name_stream.GetString();
|
||||
}
|
||||
|
||||
template <typename T = int>
|
||||
void TestNotEmpty() {
|
||||
static_assert(sizeof(T) == 0, "Empty arguments are not allowed.");
|
||||
}
|
||||
template <typename T = int>
|
||||
void TestNotEmpty(const T&) {}
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
|
||||
//
|
||||
// Stores a parameter value and later creates tests parameterized with that
|
||||
// value.
|
||||
template <class TestClass>
|
||||
class ParameterizedTestFactory : public TestFactoryBase {
|
||||
public:
|
||||
typedef typename TestClass::ParamType ParamType;
|
||||
explicit ParameterizedTestFactory(ParamType parameter) :
|
||||
parameter_(parameter) {}
|
||||
Test* CreateTest() override {
|
||||
TestClass::SetParam(¶meter_);
|
||||
return new TestClass();
|
||||
}
|
||||
|
||||
private:
|
||||
const ParamType parameter_;
|
||||
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestFactory);
|
||||
};
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
|
||||
//
|
||||
// TestMetaFactoryBase is a base class for meta-factories that create
|
||||
// test factories for passing into MakeAndRegisterTestInfo function.
|
||||
template <class ParamType>
|
||||
class TestMetaFactoryBase {
|
||||
public:
|
||||
virtual ~TestMetaFactoryBase() {}
|
||||
|
||||
virtual TestFactoryBase* CreateTestFactory(ParamType parameter) = 0;
|
||||
};
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
|
||||
//
|
||||
// TestMetaFactory creates test factories for passing into
|
||||
// MakeAndRegisterTestInfo function. Since MakeAndRegisterTestInfo receives
|
||||
// ownership of test factory pointer, same factory object cannot be passed
|
||||
// into that method twice. But ParameterizedTestSuiteInfo is going to call
|
||||
// it for each Test/Parameter value combination. Thus it needs meta factory
|
||||
// creator class.
|
||||
template <class TestSuite>
|
||||
class TestMetaFactory
|
||||
: public TestMetaFactoryBase<typename TestSuite::ParamType> {
|
||||
public:
|
||||
using ParamType = typename TestSuite::ParamType;
|
||||
|
||||
TestMetaFactory() {}
|
||||
|
||||
TestFactoryBase* CreateTestFactory(ParamType parameter) override {
|
||||
return new ParameterizedTestFactory<TestSuite>(parameter);
|
||||
}
|
||||
|
||||
private:
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(TestMetaFactory);
|
||||
};
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
|
||||
//
|
||||
// ParameterizedTestSuiteInfoBase is a generic interface
|
||||
// to ParameterizedTestSuiteInfo classes. ParameterizedTestSuiteInfoBase
|
||||
// accumulates test information provided by TEST_P macro invocations
|
||||
// and generators provided by INSTANTIATE_TEST_SUITE_P macro invocations
|
||||
// and uses that information to register all resulting test instances
|
||||
// in RegisterTests method. The ParameterizeTestSuiteRegistry class holds
|
||||
// a collection of pointers to the ParameterizedTestSuiteInfo objects
|
||||
// and calls RegisterTests() on each of them when asked.
|
||||
class ParameterizedTestSuiteInfoBase {
|
||||
public:
|
||||
virtual ~ParameterizedTestSuiteInfoBase() {}
|
||||
|
||||
// Base part of test suite name for display purposes.
|
||||
virtual const std::string& GetTestSuiteName() const = 0;
|
||||
// Test suite id to verify identity.
|
||||
virtual TypeId GetTestSuiteTypeId() const = 0;
|
||||
// UnitTest class invokes this method to register tests in this
|
||||
// test suite right before running them in RUN_ALL_TESTS macro.
|
||||
// This method should not be called more than once on any single
|
||||
// instance of a ParameterizedTestSuiteInfoBase derived class.
|
||||
virtual void RegisterTests() = 0;
|
||||
|
||||
protected:
|
||||
ParameterizedTestSuiteInfoBase() {}
|
||||
|
||||
private:
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestSuiteInfoBase);
|
||||
};
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
|
||||
//
|
||||
// Report a the name of a test_suit as safe to ignore
|
||||
// as the side effect of construction of this type.
|
||||
struct GTEST_API_ MarkAsIgnored {
|
||||
explicit MarkAsIgnored(const char* test_suite);
|
||||
};
|
||||
|
||||
GTEST_API_ void InsertSyntheticTestCase(const std::string& name,
|
||||
CodeLocation location, bool has_test_p);
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
|
||||
//
|
||||
// ParameterizedTestSuiteInfo accumulates tests obtained from TEST_P
|
||||
// macro invocations for a particular test suite and generators
|
||||
// obtained from INSTANTIATE_TEST_SUITE_P macro invocations for that
|
||||
// test suite. It registers tests with all values generated by all
|
||||
// generators when asked.
|
||||
template <class TestSuite>
|
||||
class ParameterizedTestSuiteInfo : public ParameterizedTestSuiteInfoBase {
|
||||
public:
|
||||
// ParamType and GeneratorCreationFunc are private types but are required
|
||||
// for declarations of public methods AddTestPattern() and
|
||||
// AddTestSuiteInstantiation().
|
||||
using ParamType = typename TestSuite::ParamType;
|
||||
// A function that returns an instance of appropriate generator type.
|
||||
typedef ParamGenerator<ParamType>(GeneratorCreationFunc)();
|
||||
using ParamNameGeneratorFunc = std::string(const TestParamInfo<ParamType>&);
|
||||
|
||||
explicit ParameterizedTestSuiteInfo(const char* name,
|
||||
CodeLocation code_location)
|
||||
: test_suite_name_(name), code_location_(code_location) {}
|
||||
|
||||
// Test suite base name for display purposes.
|
||||
const std::string& GetTestSuiteName() const override {
|
||||
return test_suite_name_;
|
||||
}
|
||||
// Test suite id to verify identity.
|
||||
TypeId GetTestSuiteTypeId() const override { return GetTypeId<TestSuite>(); }
|
||||
// TEST_P macro uses AddTestPattern() to record information
|
||||
// about a single test in a LocalTestInfo structure.
|
||||
// test_suite_name is the base name of the test suite (without invocation
|
||||
// prefix). test_base_name is the name of an individual test without
|
||||
// parameter index. For the test SequenceA/FooTest.DoBar/1 FooTest is
|
||||
// test suite base name and DoBar is test base name.
|
||||
void AddTestPattern(const char* test_suite_name, const char* test_base_name,
|
||||
TestMetaFactoryBase<ParamType>* meta_factory,
|
||||
CodeLocation code_location) {
|
||||
tests_.push_back(std::shared_ptr<TestInfo>(new TestInfo(
|
||||
test_suite_name, test_base_name, meta_factory, code_location)));
|
||||
}
|
||||
// INSTANTIATE_TEST_SUITE_P macro uses AddGenerator() to record information
|
||||
// about a generator.
|
||||
int AddTestSuiteInstantiation(const std::string& instantiation_name,
|
||||
GeneratorCreationFunc* func,
|
||||
ParamNameGeneratorFunc* name_func,
|
||||
const char* file, int line) {
|
||||
instantiations_.push_back(
|
||||
InstantiationInfo(instantiation_name, func, name_func, file, line));
|
||||
return 0; // Return value used only to run this method in namespace scope.
|
||||
}
|
||||
// UnitTest class invokes this method to register tests in this test suite
|
||||
// right before running tests in RUN_ALL_TESTS macro.
|
||||
// This method should not be called more than once on any single
|
||||
// instance of a ParameterizedTestSuiteInfoBase derived class.
|
||||
// UnitTest has a guard to prevent from calling this method more than once.
|
||||
void RegisterTests() override {
|
||||
bool generated_instantiations = false;
|
||||
|
||||
for (typename TestInfoContainer::iterator test_it = tests_.begin();
|
||||
test_it != tests_.end(); ++test_it) {
|
||||
std::shared_ptr<TestInfo> test_info = *test_it;
|
||||
for (typename InstantiationContainer::iterator gen_it =
|
||||
instantiations_.begin(); gen_it != instantiations_.end();
|
||||
++gen_it) {
|
||||
const std::string& instantiation_name = gen_it->name;
|
||||
ParamGenerator<ParamType> generator((*gen_it->generator)());
|
||||
ParamNameGeneratorFunc* name_func = gen_it->name_func;
|
||||
const char* file = gen_it->file;
|
||||
int line = gen_it->line;
|
||||
|
||||
std::string test_suite_name;
|
||||
if ( !instantiation_name.empty() )
|
||||
test_suite_name = instantiation_name + "/";
|
||||
test_suite_name += test_info->test_suite_base_name;
|
||||
|
||||
size_t i = 0;
|
||||
std::set<std::string> test_param_names;
|
||||
for (typename ParamGenerator<ParamType>::iterator param_it =
|
||||
generator.begin();
|
||||
param_it != generator.end(); ++param_it, ++i) {
|
||||
generated_instantiations = true;
|
||||
|
||||
Message test_name_stream;
|
||||
|
||||
std::string param_name = name_func(
|
||||
TestParamInfo<ParamType>(*param_it, i));
|
||||
|
||||
GTEST_CHECK_(IsValidParamName(param_name))
|
||||
<< "Parameterized test name '" << param_name
|
||||
<< "' is invalid, in " << file
|
||||
<< " line " << line << std::endl;
|
||||
|
||||
GTEST_CHECK_(test_param_names.count(param_name) == 0)
|
||||
<< "Duplicate parameterized test name '" << param_name
|
||||
<< "', in " << file << " line " << line << std::endl;
|
||||
|
||||
test_param_names.insert(param_name);
|
||||
|
||||
if (!test_info->test_base_name.empty()) {
|
||||
test_name_stream << test_info->test_base_name << "/";
|
||||
}
|
||||
test_name_stream << param_name;
|
||||
MakeAndRegisterTestInfo(
|
||||
test_suite_name.c_str(), test_name_stream.GetString().c_str(),
|
||||
nullptr, // No type parameter.
|
||||
PrintToString(*param_it).c_str(), test_info->code_location,
|
||||
GetTestSuiteTypeId(),
|
||||
SuiteApiResolver<TestSuite>::GetSetUpCaseOrSuite(file, line),
|
||||
SuiteApiResolver<TestSuite>::GetTearDownCaseOrSuite(file, line),
|
||||
test_info->test_meta_factory->CreateTestFactory(*param_it));
|
||||
} // for param_it
|
||||
} // for gen_it
|
||||
} // for test_it
|
||||
|
||||
if (!generated_instantiations) {
|
||||
// There are no generaotrs, or they all generate nothing ...
|
||||
InsertSyntheticTestCase(GetTestSuiteName(), code_location_,
|
||||
!tests_.empty());
|
||||
}
|
||||
} // RegisterTests
|
||||
|
||||
private:
|
||||
// LocalTestInfo structure keeps information about a single test registered
|
||||
// with TEST_P macro.
|
||||
struct TestInfo {
|
||||
TestInfo(const char* a_test_suite_base_name, const char* a_test_base_name,
|
||||
TestMetaFactoryBase<ParamType>* a_test_meta_factory,
|
||||
CodeLocation a_code_location)
|
||||
: test_suite_base_name(a_test_suite_base_name),
|
||||
test_base_name(a_test_base_name),
|
||||
test_meta_factory(a_test_meta_factory),
|
||||
code_location(a_code_location) {}
|
||||
|
||||
const std::string test_suite_base_name;
|
||||
const std::string test_base_name;
|
||||
const std::unique_ptr<TestMetaFactoryBase<ParamType> > test_meta_factory;
|
||||
const CodeLocation code_location;
|
||||
};
|
||||
using TestInfoContainer = ::std::vector<std::shared_ptr<TestInfo> >;
|
||||
// Records data received from INSTANTIATE_TEST_SUITE_P macros:
|
||||
// <Instantiation name, Sequence generator creation function,
|
||||
// Name generator function, Source file, Source line>
|
||||
struct InstantiationInfo {
|
||||
InstantiationInfo(const std::string &name_in,
|
||||
GeneratorCreationFunc* generator_in,
|
||||
ParamNameGeneratorFunc* name_func_in,
|
||||
const char* file_in,
|
||||
int line_in)
|
||||
: name(name_in),
|
||||
generator(generator_in),
|
||||
name_func(name_func_in),
|
||||
file(file_in),
|
||||
line(line_in) {}
|
||||
|
||||
std::string name;
|
||||
GeneratorCreationFunc* generator;
|
||||
ParamNameGeneratorFunc* name_func;
|
||||
const char* file;
|
||||
int line;
|
||||
};
|
||||
typedef ::std::vector<InstantiationInfo> InstantiationContainer;
|
||||
|
||||
static bool IsValidParamName(const std::string& name) {
|
||||
// Check for empty string
|
||||
if (name.empty())
|
||||
return false;
|
||||
|
||||
// Check for invalid characters
|
||||
for (std::string::size_type index = 0; index < name.size(); ++index) {
|
||||
if (!IsAlNum(name[index]) && name[index] != '_')
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string test_suite_name_;
|
||||
CodeLocation code_location_;
|
||||
TestInfoContainer tests_;
|
||||
InstantiationContainer instantiations_;
|
||||
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestSuiteInfo);
|
||||
}; // class ParameterizedTestSuiteInfo
|
||||
|
||||
// Legacy API is deprecated but still available
|
||||
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
template <class TestCase>
|
||||
using ParameterizedTestCaseInfo = ParameterizedTestSuiteInfo<TestCase>;
|
||||
#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
|
||||
//
|
||||
// ParameterizedTestSuiteRegistry contains a map of
|
||||
// ParameterizedTestSuiteInfoBase classes accessed by test suite names. TEST_P
|
||||
// and INSTANTIATE_TEST_SUITE_P macros use it to locate their corresponding
|
||||
// ParameterizedTestSuiteInfo descriptors.
|
||||
class ParameterizedTestSuiteRegistry {
|
||||
public:
|
||||
ParameterizedTestSuiteRegistry() {}
|
||||
~ParameterizedTestSuiteRegistry() {
|
||||
for (auto& test_suite_info : test_suite_infos_) {
|
||||
delete test_suite_info;
|
||||
}
|
||||
}
|
||||
|
||||
// Looks up or creates and returns a structure containing information about
|
||||
// tests and instantiations of a particular test suite.
|
||||
template <class TestSuite>
|
||||
ParameterizedTestSuiteInfo<TestSuite>* GetTestSuitePatternHolder(
|
||||
const char* test_suite_name, CodeLocation code_location) {
|
||||
ParameterizedTestSuiteInfo<TestSuite>* typed_test_info = nullptr;
|
||||
for (auto& test_suite_info : test_suite_infos_) {
|
||||
if (test_suite_info->GetTestSuiteName() == test_suite_name) {
|
||||
if (test_suite_info->GetTestSuiteTypeId() != GetTypeId<TestSuite>()) {
|
||||
// Complain about incorrect usage of Google Test facilities
|
||||
// and terminate the program since we cannot guaranty correct
|
||||
// test suite setup and tear-down in this case.
|
||||
ReportInvalidTestSuiteType(test_suite_name, code_location);
|
||||
posix::Abort();
|
||||
} else {
|
||||
// At this point we are sure that the object we found is of the same
|
||||
// type we are looking for, so we downcast it to that type
|
||||
// without further checks.
|
||||
typed_test_info = CheckedDowncastToActualType<
|
||||
ParameterizedTestSuiteInfo<TestSuite> >(test_suite_info);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (typed_test_info == nullptr) {
|
||||
typed_test_info = new ParameterizedTestSuiteInfo<TestSuite>(
|
||||
test_suite_name, code_location);
|
||||
test_suite_infos_.push_back(typed_test_info);
|
||||
}
|
||||
return typed_test_info;
|
||||
}
|
||||
void RegisterTests() {
|
||||
for (auto& test_suite_info : test_suite_infos_) {
|
||||
test_suite_info->RegisterTests();
|
||||
}
|
||||
}
|
||||
// Legacy API is deprecated but still available
|
||||
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
template <class TestCase>
|
||||
ParameterizedTestCaseInfo<TestCase>* GetTestCasePatternHolder(
|
||||
const char* test_case_name, CodeLocation code_location) {
|
||||
return GetTestSuitePatternHolder<TestCase>(test_case_name, code_location);
|
||||
}
|
||||
|
||||
#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
|
||||
private:
|
||||
using TestSuiteInfoContainer = ::std::vector<ParameterizedTestSuiteInfoBase*>;
|
||||
|
||||
TestSuiteInfoContainer test_suite_infos_;
|
||||
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestSuiteRegistry);
|
||||
};
|
||||
|
||||
// Keep track of what type-parameterized test suite are defined and
|
||||
// where as well as which are intatiated. This allows susequently
|
||||
// identifying suits that are defined but never used.
|
||||
class TypeParameterizedTestSuiteRegistry {
|
||||
public:
|
||||
// Add a suite definition
|
||||
void RegisterTestSuite(const char* test_suite_name,
|
||||
CodeLocation code_location);
|
||||
|
||||
// Add an instantiation of a suit.
|
||||
void RegisterInstantiation(const char* test_suite_name);
|
||||
|
||||
// For each suit repored as defined but not reported as instantiation,
|
||||
// emit a test that reports that fact (configurably, as an error).
|
||||
void CheckForInstantiations();
|
||||
|
||||
private:
|
||||
struct TypeParameterizedTestSuiteInfo {
|
||||
explicit TypeParameterizedTestSuiteInfo(CodeLocation c)
|
||||
: code_location(c), instantiated(false) {}
|
||||
|
||||
CodeLocation code_location;
|
||||
bool instantiated;
|
||||
};
|
||||
|
||||
std::map<std::string, TypeParameterizedTestSuiteInfo> suites_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
// Forward declarations of ValuesIn(), which is implemented in
|
||||
// include/gtest/gtest-param-test.h.
|
||||
template <class Container>
|
||||
internal::ParamGenerator<typename Container::value_type> ValuesIn(
|
||||
const Container& container);
|
||||
|
||||
namespace internal {
|
||||
// Used in the Values() function to provide polymorphic capabilities.
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4100)
|
||||
#endif
|
||||
|
||||
template <typename... Ts>
|
||||
class ValueArray {
|
||||
public:
|
||||
explicit ValueArray(Ts... v) : v_(FlatTupleConstructTag{}, std::move(v)...) {}
|
||||
|
||||
template <typename T>
|
||||
operator ParamGenerator<T>() const { // NOLINT
|
||||
return ValuesIn(MakeVector<T>(MakeIndexSequence<sizeof...(Ts)>()));
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T, size_t... I>
|
||||
std::vector<T> MakeVector(IndexSequence<I...>) const {
|
||||
return std::vector<T>{static_cast<T>(v_.template Get<I>())...};
|
||||
}
|
||||
|
||||
FlatTuple<Ts...> v_;
|
||||
};
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
template <typename... T>
|
||||
class CartesianProductGenerator
|
||||
: public ParamGeneratorInterface<::std::tuple<T...>> {
|
||||
public:
|
||||
typedef ::std::tuple<T...> ParamType;
|
||||
|
||||
CartesianProductGenerator(const std::tuple<ParamGenerator<T>...>& g)
|
||||
: generators_(g) {}
|
||||
~CartesianProductGenerator() override {}
|
||||
|
||||
ParamIteratorInterface<ParamType>* Begin() const override {
|
||||
return new Iterator(this, generators_, false);
|
||||
}
|
||||
ParamIteratorInterface<ParamType>* End() const override {
|
||||
return new Iterator(this, generators_, true);
|
||||
}
|
||||
|
||||
private:
|
||||
template <class I>
|
||||
class IteratorImpl;
|
||||
template <size_t... I>
|
||||
class IteratorImpl<IndexSequence<I...>>
|
||||
: public ParamIteratorInterface<ParamType> {
|
||||
public:
|
||||
IteratorImpl(const ParamGeneratorInterface<ParamType>* base,
|
||||
const std::tuple<ParamGenerator<T>...>& generators, bool is_end)
|
||||
: base_(base),
|
||||
begin_(std::get<I>(generators).begin()...),
|
||||
end_(std::get<I>(generators).end()...),
|
||||
current_(is_end ? end_ : begin_) {
|
||||
ComputeCurrentValue();
|
||||
}
|
||||
~IteratorImpl() override {}
|
||||
|
||||
const ParamGeneratorInterface<ParamType>* BaseGenerator() const override {
|
||||
return base_;
|
||||
}
|
||||
// Advance should not be called on beyond-of-range iterators
|
||||
// so no component iterators must be beyond end of range, either.
|
||||
void Advance() override {
|
||||
assert(!AtEnd());
|
||||
// Advance the last iterator.
|
||||
++std::get<sizeof...(T) - 1>(current_);
|
||||
// if that reaches end, propagate that up.
|
||||
AdvanceIfEnd<sizeof...(T) - 1>();
|
||||
ComputeCurrentValue();
|
||||
}
|
||||
ParamIteratorInterface<ParamType>* Clone() const override {
|
||||
return new IteratorImpl(*this);
|
||||
}
|
||||
|
||||
const ParamType* Current() const override { return current_value_.get(); }
|
||||
|
||||
bool Equals(const ParamIteratorInterface<ParamType>& other) const override {
|
||||
// Having the same base generator guarantees that the other
|
||||
// iterator is of the same type and we can downcast.
|
||||
GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
|
||||
<< "The program attempted to compare iterators "
|
||||
<< "from different generators." << std::endl;
|
||||
const IteratorImpl* typed_other =
|
||||
CheckedDowncastToActualType<const IteratorImpl>(&other);
|
||||
|
||||
// We must report iterators equal if they both point beyond their
|
||||
// respective ranges. That can happen in a variety of fashions,
|
||||
// so we have to consult AtEnd().
|
||||
if (AtEnd() && typed_other->AtEnd()) return true;
|
||||
|
||||
bool same = true;
|
||||
bool dummy[] = {
|
||||
(same = same && std::get<I>(current_) ==
|
||||
std::get<I>(typed_other->current_))...};
|
||||
(void)dummy;
|
||||
return same;
|
||||
}
|
||||
|
||||
private:
|
||||
template <size_t ThisI>
|
||||
void AdvanceIfEnd() {
|
||||
if (std::get<ThisI>(current_) != std::get<ThisI>(end_)) return;
|
||||
|
||||
bool last = ThisI == 0;
|
||||
if (last) {
|
||||
// We are done. Nothing else to propagate.
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr size_t NextI = ThisI - (ThisI != 0);
|
||||
std::get<ThisI>(current_) = std::get<ThisI>(begin_);
|
||||
++std::get<NextI>(current_);
|
||||
AdvanceIfEnd<NextI>();
|
||||
}
|
||||
|
||||
void ComputeCurrentValue() {
|
||||
if (!AtEnd())
|
||||
current_value_ = std::make_shared<ParamType>(*std::get<I>(current_)...);
|
||||
}
|
||||
bool AtEnd() const {
|
||||
bool at_end = false;
|
||||
bool dummy[] = {
|
||||
(at_end = at_end || std::get<I>(current_) == std::get<I>(end_))...};
|
||||
(void)dummy;
|
||||
return at_end;
|
||||
}
|
||||
|
||||
const ParamGeneratorInterface<ParamType>* const base_;
|
||||
std::tuple<typename ParamGenerator<T>::iterator...> begin_;
|
||||
std::tuple<typename ParamGenerator<T>::iterator...> end_;
|
||||
std::tuple<typename ParamGenerator<T>::iterator...> current_;
|
||||
std::shared_ptr<ParamType> current_value_;
|
||||
};
|
||||
|
||||
using Iterator = IteratorImpl<typename MakeIndexSequence<sizeof...(T)>::type>;
|
||||
|
||||
std::tuple<ParamGenerator<T>...> generators_;
|
||||
};
|
||||
|
||||
template <class... Gen>
|
||||
class CartesianProductHolder {
|
||||
public:
|
||||
CartesianProductHolder(const Gen&... g) : generators_(g...) {}
|
||||
template <typename... T>
|
||||
operator ParamGenerator<::std::tuple<T...>>() const {
|
||||
return ParamGenerator<::std::tuple<T...>>(
|
||||
new CartesianProductGenerator<T...>(generators_));
|
||||
}
|
||||
|
||||
private:
|
||||
std::tuple<Gen...> generators_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace testing
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright 2015, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// The Google C++ Testing and Mocking Framework (Google Test)
|
||||
//
|
||||
// This header file defines the GTEST_OS_* macro.
|
||||
// It is separate from gtest-port.h so that custom/gtest-port.h can include it.
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_
|
||||
|
||||
// Determines the platform on which Google Test is compiled.
|
||||
#ifdef __CYGWIN__
|
||||
# define GTEST_OS_CYGWIN 1
|
||||
# elif defined(__MINGW__) || defined(__MINGW32__) || defined(__MINGW64__)
|
||||
# define GTEST_OS_WINDOWS_MINGW 1
|
||||
# define GTEST_OS_WINDOWS 1
|
||||
#elif defined _WIN32
|
||||
# define GTEST_OS_WINDOWS 1
|
||||
# ifdef _WIN32_WCE
|
||||
# define GTEST_OS_WINDOWS_MOBILE 1
|
||||
# elif defined(WINAPI_FAMILY)
|
||||
# include <winapifamily.h>
|
||||
# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
# define GTEST_OS_WINDOWS_DESKTOP 1
|
||||
# elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP)
|
||||
# define GTEST_OS_WINDOWS_PHONE 1
|
||||
# elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP)
|
||||
# define GTEST_OS_WINDOWS_RT 1
|
||||
# elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_TV_TITLE)
|
||||
# define GTEST_OS_WINDOWS_PHONE 1
|
||||
# define GTEST_OS_WINDOWS_TV_TITLE 1
|
||||
# else
|
||||
// WINAPI_FAMILY defined but no known partition matched.
|
||||
// Default to desktop.
|
||||
# define GTEST_OS_WINDOWS_DESKTOP 1
|
||||
# endif
|
||||
# else
|
||||
# define GTEST_OS_WINDOWS_DESKTOP 1
|
||||
# endif // _WIN32_WCE
|
||||
#elif defined __OS2__
|
||||
# define GTEST_OS_OS2 1
|
||||
#elif defined __APPLE__
|
||||
# define GTEST_OS_MAC 1
|
||||
# include <TargetConditionals.h>
|
||||
# if TARGET_OS_IPHONE
|
||||
# define GTEST_OS_IOS 1
|
||||
# endif
|
||||
#elif defined __DragonFly__
|
||||
# define GTEST_OS_DRAGONFLY 1
|
||||
#elif defined __FreeBSD__
|
||||
# define GTEST_OS_FREEBSD 1
|
||||
#elif defined __Fuchsia__
|
||||
# define GTEST_OS_FUCHSIA 1
|
||||
#elif defined(__GLIBC__) && defined(__FreeBSD_kernel__)
|
||||
# define GTEST_OS_GNU_KFREEBSD 1
|
||||
#elif defined __linux__
|
||||
# define GTEST_OS_LINUX 1
|
||||
# if defined __ANDROID__
|
||||
# define GTEST_OS_LINUX_ANDROID 1
|
||||
# endif
|
||||
#elif defined __MVS__
|
||||
# define GTEST_OS_ZOS 1
|
||||
#elif defined(__sun) && defined(__SVR4)
|
||||
# define GTEST_OS_SOLARIS 1
|
||||
#elif defined(_AIX)
|
||||
# define GTEST_OS_AIX 1
|
||||
#elif defined(__hpux)
|
||||
# define GTEST_OS_HPUX 1
|
||||
#elif defined __native_client__
|
||||
# define GTEST_OS_NACL 1
|
||||
#elif defined __NetBSD__
|
||||
# define GTEST_OS_NETBSD 1
|
||||
#elif defined __OpenBSD__
|
||||
# define GTEST_OS_OPENBSD 1
|
||||
#elif defined __QNX__
|
||||
# define GTEST_OS_QNX 1
|
||||
#elif defined(__HAIKU__)
|
||||
#define GTEST_OS_HAIKU 1
|
||||
#elif defined ESP8266
|
||||
#define GTEST_OS_ESP8266 1
|
||||
#elif defined ESP32
|
||||
#define GTEST_OS_ESP32 1
|
||||
#elif defined(__XTENSA__)
|
||||
#define GTEST_OS_XTENSA 1
|
||||
#endif // __CYGWIN__
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,175 @@
|
||||
// Copyright 2005, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// The Google C++ Testing and Mocking Framework (Google Test)
|
||||
//
|
||||
// This header file declares the String class and functions used internally by
|
||||
// Google Test. They are subject to change without notice. They should not used
|
||||
// by code external to Google Test.
|
||||
//
|
||||
// This header file is #included by gtest-internal.h.
|
||||
// It should not be #included by other files.
|
||||
|
||||
// GOOGLETEST_CM0001 DO NOT DELETE
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_
|
||||
|
||||
#ifdef __BORLANDC__
|
||||
// string.h is not guaranteed to provide strcpy on C++ Builder.
|
||||
# include <mem.h>
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
|
||||
namespace testing {
|
||||
namespace internal {
|
||||
|
||||
// String - an abstract class holding static string utilities.
|
||||
class GTEST_API_ String {
|
||||
public:
|
||||
// Static utility methods
|
||||
|
||||
// Clones a 0-terminated C string, allocating memory using new. The
|
||||
// caller is responsible for deleting the return value using
|
||||
// delete[]. Returns the cloned string, or NULL if the input is
|
||||
// NULL.
|
||||
//
|
||||
// This is different from strdup() in string.h, which allocates
|
||||
// memory using malloc().
|
||||
static const char* CloneCString(const char* c_str);
|
||||
|
||||
#if GTEST_OS_WINDOWS_MOBILE
|
||||
// Windows CE does not have the 'ANSI' versions of Win32 APIs. To be
|
||||
// able to pass strings to Win32 APIs on CE we need to convert them
|
||||
// to 'Unicode', UTF-16.
|
||||
|
||||
// Creates a UTF-16 wide string from the given ANSI string, allocating
|
||||
// memory using new. The caller is responsible for deleting the return
|
||||
// value using delete[]. Returns the wide string, or NULL if the
|
||||
// input is NULL.
|
||||
//
|
||||
// The wide string is created using the ANSI codepage (CP_ACP) to
|
||||
// match the behaviour of the ANSI versions of Win32 calls and the
|
||||
// C runtime.
|
||||
static LPCWSTR AnsiToUtf16(const char* c_str);
|
||||
|
||||
// Creates an ANSI string from the given wide string, allocating
|
||||
// memory using new. The caller is responsible for deleting the return
|
||||
// value using delete[]. Returns the ANSI string, or NULL if the
|
||||
// input is NULL.
|
||||
//
|
||||
// The returned string is created using the ANSI codepage (CP_ACP) to
|
||||
// match the behaviour of the ANSI versions of Win32 calls and the
|
||||
// C runtime.
|
||||
static const char* Utf16ToAnsi(LPCWSTR utf16_str);
|
||||
#endif
|
||||
|
||||
// Compares two C strings. Returns true if and only if they have the same
|
||||
// content.
|
||||
//
|
||||
// Unlike strcmp(), this function can handle NULL argument(s). A
|
||||
// NULL C string is considered different to any non-NULL C string,
|
||||
// including the empty string.
|
||||
static bool CStringEquals(const char* lhs, const char* rhs);
|
||||
|
||||
// Converts a wide C string to a String using the UTF-8 encoding.
|
||||
// NULL will be converted to "(null)". If an error occurred during
|
||||
// the conversion, "(failed to convert from wide string)" is
|
||||
// returned.
|
||||
static std::string ShowWideCString(const wchar_t* wide_c_str);
|
||||
|
||||
// Compares two wide C strings. Returns true if and only if they have the
|
||||
// same content.
|
||||
//
|
||||
// Unlike wcscmp(), this function can handle NULL argument(s). A
|
||||
// NULL C string is considered different to any non-NULL C string,
|
||||
// including the empty string.
|
||||
static bool WideCStringEquals(const wchar_t* lhs, const wchar_t* rhs);
|
||||
|
||||
// Compares two C strings, ignoring case. Returns true if and only if
|
||||
// they have the same content.
|
||||
//
|
||||
// Unlike strcasecmp(), this function can handle NULL argument(s).
|
||||
// A NULL C string is considered different to any non-NULL C string,
|
||||
// including the empty string.
|
||||
static bool CaseInsensitiveCStringEquals(const char* lhs,
|
||||
const char* rhs);
|
||||
|
||||
// Compares two wide C strings, ignoring case. Returns true if and only if
|
||||
// they have the same content.
|
||||
//
|
||||
// Unlike wcscasecmp(), this function can handle NULL argument(s).
|
||||
// A NULL C string is considered different to any non-NULL wide C string,
|
||||
// including the empty string.
|
||||
// NB: The implementations on different platforms slightly differ.
|
||||
// On windows, this method uses _wcsicmp which compares according to LC_CTYPE
|
||||
// environment variable. On GNU platform this method uses wcscasecmp
|
||||
// which compares according to LC_CTYPE category of the current locale.
|
||||
// On MacOS X, it uses towlower, which also uses LC_CTYPE category of the
|
||||
// current locale.
|
||||
static bool CaseInsensitiveWideCStringEquals(const wchar_t* lhs,
|
||||
const wchar_t* rhs);
|
||||
|
||||
// Returns true if and only if the given string ends with the given suffix,
|
||||
// ignoring case. Any string is considered to end with an empty suffix.
|
||||
static bool EndsWithCaseInsensitive(
|
||||
const std::string& str, const std::string& suffix);
|
||||
|
||||
// Formats an int value as "%02d".
|
||||
static std::string FormatIntWidth2(int value); // "%02d" for width == 2
|
||||
|
||||
// Formats an int value to given width with leading zeros.
|
||||
static std::string FormatIntWidthN(int value, int width);
|
||||
|
||||
// Formats an int value as "%X".
|
||||
static std::string FormatHexInt(int value);
|
||||
|
||||
// Formats an int value as "%X".
|
||||
static std::string FormatHexUInt32(uint32_t value);
|
||||
|
||||
// Formats a byte as "%02X".
|
||||
static std::string FormatByte(unsigned char value);
|
||||
|
||||
private:
|
||||
String(); // Not meant to be instantiated.
|
||||
}; // class String
|
||||
|
||||
// Gets the content of the stringstream's buffer as an std::string. Each '\0'
|
||||
// character in the buffer is replaced with "\\0".
|
||||
GTEST_API_ std::string StringStreamToString(::std::stringstream* stream);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace testing
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_
|
||||
@@ -0,0 +1,183 @@
|
||||
// Copyright 2008 Google Inc.
|
||||
// All Rights Reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Type utilities needed for implementing typed and type-parameterized
|
||||
// tests.
|
||||
|
||||
// GOOGLETEST_CM0001 DO NOT DELETE
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_
|
||||
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
|
||||
// #ifdef __GNUC__ is too general here. It is possible to use gcc without using
|
||||
// libstdc++ (which is where cxxabi.h comes from).
|
||||
# if GTEST_HAS_CXXABI_H_
|
||||
# include <cxxabi.h>
|
||||
# elif defined(__HP_aCC)
|
||||
# include <acxx_demangle.h>
|
||||
# endif // GTEST_HASH_CXXABI_H_
|
||||
|
||||
namespace testing {
|
||||
namespace internal {
|
||||
|
||||
// Canonicalizes a given name with respect to the Standard C++ Library.
|
||||
// This handles removing the inline namespace within `std` that is
|
||||
// used by various standard libraries (e.g., `std::__1`). Names outside
|
||||
// of namespace std are returned unmodified.
|
||||
inline std::string CanonicalizeForStdLibVersioning(std::string s) {
|
||||
static const char prefix[] = "std::__";
|
||||
if (s.compare(0, strlen(prefix), prefix) == 0) {
|
||||
std::string::size_type end = s.find("::", strlen(prefix));
|
||||
if (end != s.npos) {
|
||||
// Erase everything between the initial `std` and the second `::`.
|
||||
s.erase(strlen("std"), end - strlen("std"));
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
#if GTEST_HAS_RTTI
|
||||
// GetTypeName(const std::type_info&) returns a human-readable name of type T.
|
||||
inline std::string GetTypeName(const std::type_info& type) {
|
||||
const char* const name = type.name();
|
||||
#if GTEST_HAS_CXXABI_H_ || defined(__HP_aCC)
|
||||
int status = 0;
|
||||
// gcc's implementation of typeid(T).name() mangles the type name,
|
||||
// so we have to demangle it.
|
||||
#if GTEST_HAS_CXXABI_H_
|
||||
using abi::__cxa_demangle;
|
||||
#endif // GTEST_HAS_CXXABI_H_
|
||||
char* const readable_name = __cxa_demangle(name, nullptr, nullptr, &status);
|
||||
const std::string name_str(status == 0 ? readable_name : name);
|
||||
free(readable_name);
|
||||
return CanonicalizeForStdLibVersioning(name_str);
|
||||
#else
|
||||
return name;
|
||||
#endif // GTEST_HAS_CXXABI_H_ || __HP_aCC
|
||||
}
|
||||
#endif // GTEST_HAS_RTTI
|
||||
|
||||
// GetTypeName<T>() returns a human-readable name of type T if and only if
|
||||
// RTTI is enabled, otherwise it returns a dummy type name.
|
||||
// NB: This function is also used in Google Mock, so don't move it inside of
|
||||
// the typed-test-only section below.
|
||||
template <typename T>
|
||||
std::string GetTypeName() {
|
||||
#if GTEST_HAS_RTTI
|
||||
return GetTypeName(typeid(T));
|
||||
#else
|
||||
return "<type>";
|
||||
#endif // GTEST_HAS_RTTI
|
||||
}
|
||||
|
||||
// A unique type indicating an empty node
|
||||
struct None {};
|
||||
|
||||
# define GTEST_TEMPLATE_ template <typename T> class
|
||||
|
||||
// The template "selector" struct TemplateSel<Tmpl> is used to
|
||||
// represent Tmpl, which must be a class template with one type
|
||||
// parameter, as a type. TemplateSel<Tmpl>::Bind<T>::type is defined
|
||||
// as the type Tmpl<T>. This allows us to actually instantiate the
|
||||
// template "selected" by TemplateSel<Tmpl>.
|
||||
//
|
||||
// This trick is necessary for simulating typedef for class templates,
|
||||
// which C++ doesn't support directly.
|
||||
template <GTEST_TEMPLATE_ Tmpl>
|
||||
struct TemplateSel {
|
||||
template <typename T>
|
||||
struct Bind {
|
||||
typedef Tmpl<T> type;
|
||||
};
|
||||
};
|
||||
|
||||
# define GTEST_BIND_(TmplSel, T) \
|
||||
TmplSel::template Bind<T>::type
|
||||
|
||||
template <GTEST_TEMPLATE_ Head_, GTEST_TEMPLATE_... Tail_>
|
||||
struct Templates {
|
||||
using Head = TemplateSel<Head_>;
|
||||
using Tail = Templates<Tail_...>;
|
||||
};
|
||||
|
||||
template <GTEST_TEMPLATE_ Head_>
|
||||
struct Templates<Head_> {
|
||||
using Head = TemplateSel<Head_>;
|
||||
using Tail = None;
|
||||
};
|
||||
|
||||
// Tuple-like type lists
|
||||
template <typename Head_, typename... Tail_>
|
||||
struct Types {
|
||||
using Head = Head_;
|
||||
using Tail = Types<Tail_...>;
|
||||
};
|
||||
|
||||
template <typename Head_>
|
||||
struct Types<Head_> {
|
||||
using Head = Head_;
|
||||
using Tail = None;
|
||||
};
|
||||
|
||||
// Helper metafunctions to tell apart a single type from types
|
||||
// generated by ::testing::Types
|
||||
template <typename... Ts>
|
||||
struct ProxyTypeList {
|
||||
using type = Types<Ts...>;
|
||||
};
|
||||
|
||||
template <typename>
|
||||
struct is_proxy_type_list : std::false_type {};
|
||||
|
||||
template <typename... Ts>
|
||||
struct is_proxy_type_list<ProxyTypeList<Ts...>> : std::true_type {};
|
||||
|
||||
// Generator which conditionally creates type lists.
|
||||
// It recognizes if a requested type list should be created
|
||||
// and prevents creating a new type list nested within another one.
|
||||
template <typename T>
|
||||
struct GenerateTypeList {
|
||||
private:
|
||||
using proxy = typename std::conditional<is_proxy_type_list<T>::value, T,
|
||||
ProxyTypeList<T>>::type;
|
||||
|
||||
public:
|
||||
using type = typename proxy::type;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
template <typename... Ts>
|
||||
using Types = internal::ProxyTypeList<Ts...>;
|
||||
|
||||
} // namespace testing
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// Google C++ Testing and Mocking Framework (Google Test)
|
||||
//
|
||||
// Sometimes it's desirable to build Google Test by compiling a single file.
|
||||
// This file serves this purpose.
|
||||
|
||||
// This line ensures that gtest.h can be compiled on its own, even
|
||||
// when it's fused.
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
// The following lines pull in the real gtest *.cc files.
|
||||
#include "src/gtest.cc"
|
||||
#include "src/gtest-death-test.cc"
|
||||
#include "src/gtest-filepath.cc"
|
||||
#include "src/gtest-matchers.cc"
|
||||
#include "src/gtest-port.cc"
|
||||
#include "src/gtest-printers.cc"
|
||||
#include "src/gtest-test-part.cc"
|
||||
#include "src/gtest-typed-test.cc"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,369 @@
|
||||
// Copyright 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "gtest/internal/gtest-filepath.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
#include "gtest/gtest-message.h"
|
||||
|
||||
#if GTEST_OS_WINDOWS_MOBILE
|
||||
# include <windows.h>
|
||||
#elif GTEST_OS_WINDOWS
|
||||
# include <direct.h>
|
||||
# include <io.h>
|
||||
#else
|
||||
# include <limits.h>
|
||||
# include <climits> // Some Linux distributions define PATH_MAX here.
|
||||
#endif // GTEST_OS_WINDOWS_MOBILE
|
||||
|
||||
#include "gtest/internal/gtest-string.h"
|
||||
|
||||
#if GTEST_OS_WINDOWS
|
||||
# define GTEST_PATH_MAX_ _MAX_PATH
|
||||
#elif defined(PATH_MAX)
|
||||
# define GTEST_PATH_MAX_ PATH_MAX
|
||||
#elif defined(_XOPEN_PATH_MAX)
|
||||
# define GTEST_PATH_MAX_ _XOPEN_PATH_MAX
|
||||
#else
|
||||
# define GTEST_PATH_MAX_ _POSIX_PATH_MAX
|
||||
#endif // GTEST_OS_WINDOWS
|
||||
|
||||
namespace testing {
|
||||
namespace internal {
|
||||
|
||||
#if GTEST_OS_WINDOWS
|
||||
// On Windows, '\\' is the standard path separator, but many tools and the
|
||||
// Windows API also accept '/' as an alternate path separator. Unless otherwise
|
||||
// noted, a file path can contain either kind of path separators, or a mixture
|
||||
// of them.
|
||||
const char kPathSeparator = '\\';
|
||||
const char kAlternatePathSeparator = '/';
|
||||
const char kAlternatePathSeparatorString[] = "/";
|
||||
# if GTEST_OS_WINDOWS_MOBILE
|
||||
// Windows CE doesn't have a current directory. You should not use
|
||||
// the current directory in tests on Windows CE, but this at least
|
||||
// provides a reasonable fallback.
|
||||
const char kCurrentDirectoryString[] = "\\";
|
||||
// Windows CE doesn't define INVALID_FILE_ATTRIBUTES
|
||||
const DWORD kInvalidFileAttributes = 0xffffffff;
|
||||
# else
|
||||
const char kCurrentDirectoryString[] = ".\\";
|
||||
# endif // GTEST_OS_WINDOWS_MOBILE
|
||||
#else
|
||||
const char kPathSeparator = '/';
|
||||
const char kCurrentDirectoryString[] = "./";
|
||||
#endif // GTEST_OS_WINDOWS
|
||||
|
||||
// Returns whether the given character is a valid path separator.
|
||||
static bool IsPathSeparator(char c) {
|
||||
#if GTEST_HAS_ALT_PATH_SEP_
|
||||
return (c == kPathSeparator) || (c == kAlternatePathSeparator);
|
||||
#else
|
||||
return c == kPathSeparator;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns the current working directory, or "" if unsuccessful.
|
||||
FilePath FilePath::GetCurrentDir() {
|
||||
#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_PHONE || \
|
||||
GTEST_OS_WINDOWS_RT || GTEST_OS_ESP8266 || GTEST_OS_ESP32 || \
|
||||
GTEST_OS_XTENSA
|
||||
// These platforms do not have a current directory, so we just return
|
||||
// something reasonable.
|
||||
return FilePath(kCurrentDirectoryString);
|
||||
#elif GTEST_OS_WINDOWS
|
||||
char cwd[GTEST_PATH_MAX_ + 1] = { '\0' };
|
||||
return FilePath(_getcwd(cwd, sizeof(cwd)) == nullptr ? "" : cwd);
|
||||
#else
|
||||
char cwd[GTEST_PATH_MAX_ + 1] = { '\0' };
|
||||
char* result = getcwd(cwd, sizeof(cwd));
|
||||
# if GTEST_OS_NACL
|
||||
// getcwd will likely fail in NaCl due to the sandbox, so return something
|
||||
// reasonable. The user may have provided a shim implementation for getcwd,
|
||||
// however, so fallback only when failure is detected.
|
||||
return FilePath(result == nullptr ? kCurrentDirectoryString : cwd);
|
||||
# endif // GTEST_OS_NACL
|
||||
return FilePath(result == nullptr ? "" : cwd);
|
||||
#endif // GTEST_OS_WINDOWS_MOBILE
|
||||
}
|
||||
|
||||
// Returns a copy of the FilePath with the case-insensitive extension removed.
|
||||
// Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns
|
||||
// FilePath("dir/file"). If a case-insensitive extension is not
|
||||
// found, returns a copy of the original FilePath.
|
||||
FilePath FilePath::RemoveExtension(const char* extension) const {
|
||||
const std::string dot_extension = std::string(".") + extension;
|
||||
if (String::EndsWithCaseInsensitive(pathname_, dot_extension)) {
|
||||
return FilePath(pathname_.substr(
|
||||
0, pathname_.length() - dot_extension.length()));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Returns a pointer to the last occurrence of a valid path separator in
|
||||
// the FilePath. On Windows, for example, both '/' and '\' are valid path
|
||||
// separators. Returns NULL if no path separator was found.
|
||||
const char* FilePath::FindLastPathSeparator() const {
|
||||
const char* const last_sep = strrchr(c_str(), kPathSeparator);
|
||||
#if GTEST_HAS_ALT_PATH_SEP_
|
||||
const char* const last_alt_sep = strrchr(c_str(), kAlternatePathSeparator);
|
||||
// Comparing two pointers of which only one is NULL is undefined.
|
||||
if (last_alt_sep != nullptr &&
|
||||
(last_sep == nullptr || last_alt_sep > last_sep)) {
|
||||
return last_alt_sep;
|
||||
}
|
||||
#endif
|
||||
return last_sep;
|
||||
}
|
||||
|
||||
// Returns a copy of the FilePath with the directory part removed.
|
||||
// Example: FilePath("path/to/file").RemoveDirectoryName() returns
|
||||
// FilePath("file"). If there is no directory part ("just_a_file"), it returns
|
||||
// the FilePath unmodified. If there is no file part ("just_a_dir/") it
|
||||
// returns an empty FilePath ("").
|
||||
// On Windows platform, '\' is the path separator, otherwise it is '/'.
|
||||
FilePath FilePath::RemoveDirectoryName() const {
|
||||
const char* const last_sep = FindLastPathSeparator();
|
||||
return last_sep ? FilePath(last_sep + 1) : *this;
|
||||
}
|
||||
|
||||
// RemoveFileName returns the directory path with the filename removed.
|
||||
// Example: FilePath("path/to/file").RemoveFileName() returns "path/to/".
|
||||
// If the FilePath is "a_file" or "/a_file", RemoveFileName returns
|
||||
// FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does
|
||||
// not have a file, like "just/a/dir/", it returns the FilePath unmodified.
|
||||
// On Windows platform, '\' is the path separator, otherwise it is '/'.
|
||||
FilePath FilePath::RemoveFileName() const {
|
||||
const char* const last_sep = FindLastPathSeparator();
|
||||
std::string dir;
|
||||
if (last_sep) {
|
||||
dir = std::string(c_str(), static_cast<size_t>(last_sep + 1 - c_str()));
|
||||
} else {
|
||||
dir = kCurrentDirectoryString;
|
||||
}
|
||||
return FilePath(dir);
|
||||
}
|
||||
|
||||
// Helper functions for naming files in a directory for xml output.
|
||||
|
||||
// Given directory = "dir", base_name = "test", number = 0,
|
||||
// extension = "xml", returns "dir/test.xml". If number is greater
|
||||
// than zero (e.g., 12), returns "dir/test_12.xml".
|
||||
// On Windows platform, uses \ as the separator rather than /.
|
||||
FilePath FilePath::MakeFileName(const FilePath& directory,
|
||||
const FilePath& base_name,
|
||||
int number,
|
||||
const char* extension) {
|
||||
std::string file;
|
||||
if (number == 0) {
|
||||
file = base_name.string() + "." + extension;
|
||||
} else {
|
||||
file = base_name.string() + "_" + StreamableToString(number)
|
||||
+ "." + extension;
|
||||
}
|
||||
return ConcatPaths(directory, FilePath(file));
|
||||
}
|
||||
|
||||
// Given directory = "dir", relative_path = "test.xml", returns "dir/test.xml".
|
||||
// On Windows, uses \ as the separator rather than /.
|
||||
FilePath FilePath::ConcatPaths(const FilePath& directory,
|
||||
const FilePath& relative_path) {
|
||||
if (directory.IsEmpty())
|
||||
return relative_path;
|
||||
const FilePath dir(directory.RemoveTrailingPathSeparator());
|
||||
return FilePath(dir.string() + kPathSeparator + relative_path.string());
|
||||
}
|
||||
|
||||
// Returns true if pathname describes something findable in the file-system,
|
||||
// either a file, directory, or whatever.
|
||||
bool FilePath::FileOrDirectoryExists() const {
|
||||
#if GTEST_OS_WINDOWS_MOBILE
|
||||
LPCWSTR unicode = String::AnsiToUtf16(pathname_.c_str());
|
||||
const DWORD attributes = GetFileAttributes(unicode);
|
||||
delete [] unicode;
|
||||
return attributes != kInvalidFileAttributes;
|
||||
#else
|
||||
posix::StatStruct file_stat{};
|
||||
return posix::Stat(pathname_.c_str(), &file_stat) == 0;
|
||||
#endif // GTEST_OS_WINDOWS_MOBILE
|
||||
}
|
||||
|
||||
// Returns true if pathname describes a directory in the file-system
|
||||
// that exists.
|
||||
bool FilePath::DirectoryExists() const {
|
||||
bool result = false;
|
||||
#if GTEST_OS_WINDOWS
|
||||
// Don't strip off trailing separator if path is a root directory on
|
||||
// Windows (like "C:\\").
|
||||
const FilePath& path(IsRootDirectory() ? *this :
|
||||
RemoveTrailingPathSeparator());
|
||||
#else
|
||||
const FilePath& path(*this);
|
||||
#endif
|
||||
|
||||
#if GTEST_OS_WINDOWS_MOBILE
|
||||
LPCWSTR unicode = String::AnsiToUtf16(path.c_str());
|
||||
const DWORD attributes = GetFileAttributes(unicode);
|
||||
delete [] unicode;
|
||||
if ((attributes != kInvalidFileAttributes) &&
|
||||
(attributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
result = true;
|
||||
}
|
||||
#else
|
||||
posix::StatStruct file_stat{};
|
||||
result = posix::Stat(path.c_str(), &file_stat) == 0 &&
|
||||
posix::IsDir(file_stat);
|
||||
#endif // GTEST_OS_WINDOWS_MOBILE
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns true if pathname describes a root directory. (Windows has one
|
||||
// root directory per disk drive.)
|
||||
bool FilePath::IsRootDirectory() const {
|
||||
#if GTEST_OS_WINDOWS
|
||||
return pathname_.length() == 3 && IsAbsolutePath();
|
||||
#else
|
||||
return pathname_.length() == 1 && IsPathSeparator(pathname_.c_str()[0]);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns true if pathname describes an absolute path.
|
||||
bool FilePath::IsAbsolutePath() const {
|
||||
const char* const name = pathname_.c_str();
|
||||
#if GTEST_OS_WINDOWS
|
||||
return pathname_.length() >= 3 &&
|
||||
((name[0] >= 'a' && name[0] <= 'z') ||
|
||||
(name[0] >= 'A' && name[0] <= 'Z')) &&
|
||||
name[1] == ':' &&
|
||||
IsPathSeparator(name[2]);
|
||||
#else
|
||||
return IsPathSeparator(name[0]);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns a pathname for a file that does not currently exist. The pathname
|
||||
// will be directory/base_name.extension or
|
||||
// directory/base_name_<number>.extension if directory/base_name.extension
|
||||
// already exists. The number will be incremented until a pathname is found
|
||||
// that does not already exist.
|
||||
// Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'.
|
||||
// There could be a race condition if two or more processes are calling this
|
||||
// function at the same time -- they could both pick the same filename.
|
||||
FilePath FilePath::GenerateUniqueFileName(const FilePath& directory,
|
||||
const FilePath& base_name,
|
||||
const char* extension) {
|
||||
FilePath full_pathname;
|
||||
int number = 0;
|
||||
do {
|
||||
full_pathname.Set(MakeFileName(directory, base_name, number++, extension));
|
||||
} while (full_pathname.FileOrDirectoryExists());
|
||||
return full_pathname;
|
||||
}
|
||||
|
||||
// Returns true if FilePath ends with a path separator, which indicates that
|
||||
// it is intended to represent a directory. Returns false otherwise.
|
||||
// This does NOT check that a directory (or file) actually exists.
|
||||
bool FilePath::IsDirectory() const {
|
||||
return !pathname_.empty() &&
|
||||
IsPathSeparator(pathname_.c_str()[pathname_.length() - 1]);
|
||||
}
|
||||
|
||||
// Create directories so that path exists. Returns true if successful or if
|
||||
// the directories already exist; returns false if unable to create directories
|
||||
// for any reason.
|
||||
bool FilePath::CreateDirectoriesRecursively() const {
|
||||
if (!this->IsDirectory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pathname_.length() == 0 || this->DirectoryExists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const FilePath parent(this->RemoveTrailingPathSeparator().RemoveFileName());
|
||||
return parent.CreateDirectoriesRecursively() && this->CreateFolder();
|
||||
}
|
||||
|
||||
// Create the directory so that path exists. Returns true if successful or
|
||||
// if the directory already exists; returns false if unable to create the
|
||||
// directory for any reason, including if the parent directory does not
|
||||
// exist. Not named "CreateDirectory" because that's a macro on Windows.
|
||||
bool FilePath::CreateFolder() const {
|
||||
#if GTEST_OS_WINDOWS_MOBILE
|
||||
FilePath removed_sep(this->RemoveTrailingPathSeparator());
|
||||
LPCWSTR unicode = String::AnsiToUtf16(removed_sep.c_str());
|
||||
int result = CreateDirectory(unicode, nullptr) ? 0 : -1;
|
||||
delete [] unicode;
|
||||
#elif GTEST_OS_WINDOWS
|
||||
int result = _mkdir(pathname_.c_str());
|
||||
#elif GTEST_OS_ESP8266 || GTEST_OS_XTENSA
|
||||
// do nothing
|
||||
int result = 0;
|
||||
#else
|
||||
int result = mkdir(pathname_.c_str(), 0777);
|
||||
#endif // GTEST_OS_WINDOWS_MOBILE
|
||||
|
||||
if (result == -1) {
|
||||
return this->DirectoryExists(); // An error is OK if the directory exists.
|
||||
}
|
||||
return true; // No error.
|
||||
}
|
||||
|
||||
// If input name has a trailing separator character, remove it and return the
|
||||
// name, otherwise return the name string unmodified.
|
||||
// On Windows platform, uses \ as the separator, other platforms use /.
|
||||
FilePath FilePath::RemoveTrailingPathSeparator() const {
|
||||
return IsDirectory()
|
||||
? FilePath(pathname_.substr(0, pathname_.length() - 1))
|
||||
: *this;
|
||||
}
|
||||
|
||||
// Removes any redundant separators that might be in the pathname.
|
||||
// For example, "bar///foo" becomes "bar/foo". Does not eliminate other
|
||||
// redundancies that might be in a pathname involving "." or "..".
|
||||
void FilePath::Normalize() {
|
||||
auto out = pathname_.begin();
|
||||
|
||||
for (const char character : pathname_) {
|
||||
if (!IsPathSeparator(character)) {
|
||||
*(out++) = character;
|
||||
} else if (out == pathname_.begin() || *std::prev(out) != kPathSeparator) {
|
||||
*(out++) = kPathSeparator;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
pathname_.erase(out, pathname_.end());
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace testing
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,97 @@
|
||||
// Copyright 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// The Google C++ Testing and Mocking Framework (Google Test)
|
||||
//
|
||||
// This file implements just enough of the matcher interface to allow
|
||||
// EXPECT_DEATH and friends to accept a matcher argument.
|
||||
|
||||
#include "gtest/internal/gtest-internal.h"
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
#include "gtest/gtest-matchers.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace testing {
|
||||
|
||||
// Constructs a matcher that matches a const std::string& whose value is
|
||||
// equal to s.
|
||||
Matcher<const std::string&>::Matcher(const std::string& s) { *this = Eq(s); }
|
||||
|
||||
// Constructs a matcher that matches a const std::string& whose value is
|
||||
// equal to s.
|
||||
Matcher<const std::string&>::Matcher(const char* s) {
|
||||
*this = Eq(std::string(s));
|
||||
}
|
||||
|
||||
// Constructs a matcher that matches a std::string whose value is equal to
|
||||
// s.
|
||||
Matcher<std::string>::Matcher(const std::string& s) { *this = Eq(s); }
|
||||
|
||||
// Constructs a matcher that matches a std::string whose value is equal to
|
||||
// s.
|
||||
Matcher<std::string>::Matcher(const char* s) { *this = Eq(std::string(s)); }
|
||||
|
||||
#if GTEST_INTERNAL_HAS_STRING_VIEW
|
||||
// Constructs a matcher that matches a const StringView& whose value is
|
||||
// equal to s.
|
||||
Matcher<const internal::StringView&>::Matcher(const std::string& s) {
|
||||
*this = Eq(s);
|
||||
}
|
||||
|
||||
// Constructs a matcher that matches a const StringView& whose value is
|
||||
// equal to s.
|
||||
Matcher<const internal::StringView&>::Matcher(const char* s) {
|
||||
*this = Eq(std::string(s));
|
||||
}
|
||||
|
||||
// Constructs a matcher that matches a const StringView& whose value is
|
||||
// equal to s.
|
||||
Matcher<const internal::StringView&>::Matcher(internal::StringView s) {
|
||||
*this = Eq(std::string(s));
|
||||
}
|
||||
|
||||
// Constructs a matcher that matches a StringView whose value is equal to
|
||||
// s.
|
||||
Matcher<internal::StringView>::Matcher(const std::string& s) { *this = Eq(s); }
|
||||
|
||||
// Constructs a matcher that matches a StringView whose value is equal to
|
||||
// s.
|
||||
Matcher<internal::StringView>::Matcher(const char* s) {
|
||||
*this = Eq(std::string(s));
|
||||
}
|
||||
|
||||
// Constructs a matcher that matches a StringView whose value is equal to
|
||||
// s.
|
||||
Matcher<internal::StringView>::Matcher(internal::StringView s) {
|
||||
*this = Eq(std::string(s));
|
||||
}
|
||||
#endif // GTEST_INTERNAL_HAS_STRING_VIEW
|
||||
|
||||
} // namespace testing
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,533 @@
|
||||
// Copyright 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
// Google Test - The Google C++ Testing and Mocking Framework
|
||||
//
|
||||
// This file implements a universal value printer that can print a
|
||||
// value of any type T:
|
||||
//
|
||||
// void ::testing::internal::UniversalPrinter<T>::Print(value, ostream_ptr);
|
||||
//
|
||||
// It uses the << operator when possible, and prints the bytes in the
|
||||
// object otherwise. A user can override its behavior for a class
|
||||
// type Foo by defining either operator<<(::std::ostream&, const Foo&)
|
||||
// or void PrintTo(const Foo&, ::std::ostream*) in the namespace that
|
||||
// defines Foo.
|
||||
|
||||
#include "gtest/gtest-printers.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <cwchar>
|
||||
#include <ostream> // NOLINT
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
#include "src/gtest-internal-inl.h"
|
||||
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::std::ostream;
|
||||
|
||||
// Prints a segment of bytes in the given object.
|
||||
GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
|
||||
GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
|
||||
GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_
|
||||
GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
|
||||
void PrintByteSegmentInObjectTo(const unsigned char* obj_bytes, size_t start,
|
||||
size_t count, ostream* os) {
|
||||
char text[5] = "";
|
||||
for (size_t i = 0; i != count; i++) {
|
||||
const size_t j = start + i;
|
||||
if (i != 0) {
|
||||
// Organizes the bytes into groups of 2 for easy parsing by
|
||||
// human.
|
||||
if ((j % 2) == 0)
|
||||
*os << ' ';
|
||||
else
|
||||
*os << '-';
|
||||
}
|
||||
GTEST_SNPRINTF_(text, sizeof(text), "%02X", obj_bytes[j]);
|
||||
*os << text;
|
||||
}
|
||||
}
|
||||
|
||||
// Prints the bytes in the given value to the given ostream.
|
||||
void PrintBytesInObjectToImpl(const unsigned char* obj_bytes, size_t count,
|
||||
ostream* os) {
|
||||
// Tells the user how big the object is.
|
||||
*os << count << "-byte object <";
|
||||
|
||||
const size_t kThreshold = 132;
|
||||
const size_t kChunkSize = 64;
|
||||
// If the object size is bigger than kThreshold, we'll have to omit
|
||||
// some details by printing only the first and the last kChunkSize
|
||||
// bytes.
|
||||
if (count < kThreshold) {
|
||||
PrintByteSegmentInObjectTo(obj_bytes, 0, count, os);
|
||||
} else {
|
||||
PrintByteSegmentInObjectTo(obj_bytes, 0, kChunkSize, os);
|
||||
*os << " ... ";
|
||||
// Rounds up to 2-byte boundary.
|
||||
const size_t resume_pos = (count - kChunkSize + 1)/2*2;
|
||||
PrintByteSegmentInObjectTo(obj_bytes, resume_pos, count - resume_pos, os);
|
||||
}
|
||||
*os << ">";
|
||||
}
|
||||
|
||||
// Helpers for widening a character to char32_t. Since the standard does not
|
||||
// specify if char / wchar_t is signed or unsigned, it is important to first
|
||||
// convert it to the unsigned type of the same width before widening it to
|
||||
// char32_t.
|
||||
template <typename CharType>
|
||||
char32_t ToChar32(CharType in) {
|
||||
return static_cast<char32_t>(
|
||||
static_cast<typename std::make_unsigned<CharType>::type>(in));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Delegates to PrintBytesInObjectToImpl() to print the bytes in the
|
||||
// given object. The delegation simplifies the implementation, which
|
||||
// uses the << operator and thus is easier done outside of the
|
||||
// ::testing::internal namespace, which contains a << operator that
|
||||
// sometimes conflicts with the one in STL.
|
||||
void PrintBytesInObjectTo(const unsigned char* obj_bytes, size_t count,
|
||||
ostream* os) {
|
||||
PrintBytesInObjectToImpl(obj_bytes, count, os);
|
||||
}
|
||||
|
||||
// Depending on the value of a char (or wchar_t), we print it in one
|
||||
// of three formats:
|
||||
// - as is if it's a printable ASCII (e.g. 'a', '2', ' '),
|
||||
// - as a hexadecimal escape sequence (e.g. '\x7F'), or
|
||||
// - as a special escape sequence (e.g. '\r', '\n').
|
||||
enum CharFormat {
|
||||
kAsIs,
|
||||
kHexEscape,
|
||||
kSpecialEscape
|
||||
};
|
||||
|
||||
// Returns true if c is a printable ASCII character. We test the
|
||||
// value of c directly instead of calling isprint(), which is buggy on
|
||||
// Windows Mobile.
|
||||
inline bool IsPrintableAscii(char32_t c) { return 0x20 <= c && c <= 0x7E; }
|
||||
|
||||
// Prints c (of type char, char8_t, char16_t, char32_t, or wchar_t) as a
|
||||
// character literal without the quotes, escaping it when necessary; returns how
|
||||
// c was formatted.
|
||||
template <typename Char>
|
||||
static CharFormat PrintAsCharLiteralTo(Char c, ostream* os) {
|
||||
const char32_t u_c = ToChar32(c);
|
||||
switch (u_c) {
|
||||
case L'\0':
|
||||
*os << "\\0";
|
||||
break;
|
||||
case L'\'':
|
||||
*os << "\\'";
|
||||
break;
|
||||
case L'\\':
|
||||
*os << "\\\\";
|
||||
break;
|
||||
case L'\a':
|
||||
*os << "\\a";
|
||||
break;
|
||||
case L'\b':
|
||||
*os << "\\b";
|
||||
break;
|
||||
case L'\f':
|
||||
*os << "\\f";
|
||||
break;
|
||||
case L'\n':
|
||||
*os << "\\n";
|
||||
break;
|
||||
case L'\r':
|
||||
*os << "\\r";
|
||||
break;
|
||||
case L'\t':
|
||||
*os << "\\t";
|
||||
break;
|
||||
case L'\v':
|
||||
*os << "\\v";
|
||||
break;
|
||||
default:
|
||||
if (IsPrintableAscii(u_c)) {
|
||||
*os << static_cast<char>(c);
|
||||
return kAsIs;
|
||||
} else {
|
||||
ostream::fmtflags flags = os->flags();
|
||||
*os << "\\x" << std::hex << std::uppercase << static_cast<int>(u_c);
|
||||
os->flags(flags);
|
||||
return kHexEscape;
|
||||
}
|
||||
}
|
||||
return kSpecialEscape;
|
||||
}
|
||||
|
||||
// Prints a char32_t c as if it's part of a string literal, escaping it when
|
||||
// necessary; returns how c was formatted.
|
||||
static CharFormat PrintAsStringLiteralTo(char32_t c, ostream* os) {
|
||||
switch (c) {
|
||||
case L'\'':
|
||||
*os << "'";
|
||||
return kAsIs;
|
||||
case L'"':
|
||||
*os << "\\\"";
|
||||
return kSpecialEscape;
|
||||
default:
|
||||
return PrintAsCharLiteralTo(c, os);
|
||||
}
|
||||
}
|
||||
|
||||
static const char* GetCharWidthPrefix(char) {
|
||||
return "";
|
||||
}
|
||||
|
||||
static const char* GetCharWidthPrefix(signed char) {
|
||||
return "";
|
||||
}
|
||||
|
||||
static const char* GetCharWidthPrefix(unsigned char) {
|
||||
return "";
|
||||
}
|
||||
|
||||
#ifdef __cpp_char8_t
|
||||
static const char* GetCharWidthPrefix(char8_t) {
|
||||
return "u8";
|
||||
}
|
||||
#endif
|
||||
|
||||
static const char* GetCharWidthPrefix(char16_t) {
|
||||
return "u";
|
||||
}
|
||||
|
||||
static const char* GetCharWidthPrefix(char32_t) {
|
||||
return "U";
|
||||
}
|
||||
|
||||
static const char* GetCharWidthPrefix(wchar_t) {
|
||||
return "L";
|
||||
}
|
||||
|
||||
// Prints a char c as if it's part of a string literal, escaping it when
|
||||
// necessary; returns how c was formatted.
|
||||
static CharFormat PrintAsStringLiteralTo(char c, ostream* os) {
|
||||
return PrintAsStringLiteralTo(ToChar32(c), os);
|
||||
}
|
||||
|
||||
#ifdef __cpp_char8_t
|
||||
static CharFormat PrintAsStringLiteralTo(char8_t c, ostream* os) {
|
||||
return PrintAsStringLiteralTo(ToChar32(c), os);
|
||||
}
|
||||
#endif
|
||||
|
||||
static CharFormat PrintAsStringLiteralTo(char16_t c, ostream* os) {
|
||||
return PrintAsStringLiteralTo(ToChar32(c), os);
|
||||
}
|
||||
|
||||
static CharFormat PrintAsStringLiteralTo(wchar_t c, ostream* os) {
|
||||
return PrintAsStringLiteralTo(ToChar32(c), os);
|
||||
}
|
||||
|
||||
// Prints a character c (of type char, char8_t, char16_t, char32_t, or wchar_t)
|
||||
// and its code. '\0' is printed as "'\\0'", other unprintable characters are
|
||||
// also properly escaped using the standard C++ escape sequence.
|
||||
template <typename Char>
|
||||
void PrintCharAndCodeTo(Char c, ostream* os) {
|
||||
// First, print c as a literal in the most readable form we can find.
|
||||
*os << GetCharWidthPrefix(c) << "'";
|
||||
const CharFormat format = PrintAsCharLiteralTo(c, os);
|
||||
*os << "'";
|
||||
|
||||
// To aid user debugging, we also print c's code in decimal, unless
|
||||
// it's 0 (in which case c was printed as '\\0', making the code
|
||||
// obvious).
|
||||
if (c == 0)
|
||||
return;
|
||||
*os << " (" << static_cast<int>(c);
|
||||
|
||||
// For more convenience, we print c's code again in hexadecimal,
|
||||
// unless c was already printed in the form '\x##' or the code is in
|
||||
// [1, 9].
|
||||
if (format == kHexEscape || (1 <= c && c <= 9)) {
|
||||
// Do nothing.
|
||||
} else {
|
||||
*os << ", 0x" << String::FormatHexInt(static_cast<int>(c));
|
||||
}
|
||||
*os << ")";
|
||||
}
|
||||
|
||||
void PrintTo(unsigned char c, ::std::ostream* os) { PrintCharAndCodeTo(c, os); }
|
||||
void PrintTo(signed char c, ::std::ostream* os) { PrintCharAndCodeTo(c, os); }
|
||||
|
||||
// Prints a wchar_t as a symbol if it is printable or as its internal
|
||||
// code otherwise and also as its code. L'\0' is printed as "L'\\0'".
|
||||
void PrintTo(wchar_t wc, ostream* os) { PrintCharAndCodeTo(wc, os); }
|
||||
|
||||
// TODO(dcheng): Consider making this delegate to PrintCharAndCodeTo() as well.
|
||||
void PrintTo(char32_t c, ::std::ostream* os) {
|
||||
*os << std::hex << "U+" << std::uppercase << std::setfill('0') << std::setw(4)
|
||||
<< static_cast<uint32_t>(c);
|
||||
}
|
||||
|
||||
// Prints the given array of characters to the ostream. CharType must be either
|
||||
// char, char8_t, char16_t, char32_t, or wchar_t.
|
||||
// The array starts at begin, the length is len, it may include '\0' characters
|
||||
// and may not be NUL-terminated.
|
||||
template <typename CharType>
|
||||
GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
|
||||
GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
|
||||
GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_
|
||||
GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
|
||||
static CharFormat PrintCharsAsStringTo(
|
||||
const CharType* begin, size_t len, ostream* os) {
|
||||
const char* const quote_prefix = GetCharWidthPrefix(*begin);
|
||||
*os << quote_prefix << "\"";
|
||||
bool is_previous_hex = false;
|
||||
CharFormat print_format = kAsIs;
|
||||
for (size_t index = 0; index < len; ++index) {
|
||||
const CharType cur = begin[index];
|
||||
if (is_previous_hex && IsXDigit(cur)) {
|
||||
// Previous character is of '\x..' form and this character can be
|
||||
// interpreted as another hexadecimal digit in its number. Break string to
|
||||
// disambiguate.
|
||||
*os << "\" " << quote_prefix << "\"";
|
||||
}
|
||||
is_previous_hex = PrintAsStringLiteralTo(cur, os) == kHexEscape;
|
||||
// Remember if any characters required hex escaping.
|
||||
if (is_previous_hex) {
|
||||
print_format = kHexEscape;
|
||||
}
|
||||
}
|
||||
*os << "\"";
|
||||
return print_format;
|
||||
}
|
||||
|
||||
// Prints a (const) char/wchar_t array of 'len' elements, starting at address
|
||||
// 'begin'. CharType must be either char or wchar_t.
|
||||
template <typename CharType>
|
||||
GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
|
||||
GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
|
||||
GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_
|
||||
GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
|
||||
static void UniversalPrintCharArray(
|
||||
const CharType* begin, size_t len, ostream* os) {
|
||||
// The code
|
||||
// const char kFoo[] = "foo";
|
||||
// generates an array of 4, not 3, elements, with the last one being '\0'.
|
||||
//
|
||||
// Therefore when printing a char array, we don't print the last element if
|
||||
// it's '\0', such that the output matches the string literal as it's
|
||||
// written in the source code.
|
||||
if (len > 0 && begin[len - 1] == '\0') {
|
||||
PrintCharsAsStringTo(begin, len - 1, os);
|
||||
return;
|
||||
}
|
||||
|
||||
// If, however, the last element in the array is not '\0', e.g.
|
||||
// const char kFoo[] = { 'f', 'o', 'o' };
|
||||
// we must print the entire array. We also print a message to indicate
|
||||
// that the array is not NUL-terminated.
|
||||
PrintCharsAsStringTo(begin, len, os);
|
||||
*os << " (no terminating NUL)";
|
||||
}
|
||||
|
||||
// Prints a (const) char array of 'len' elements, starting at address 'begin'.
|
||||
void UniversalPrintArray(const char* begin, size_t len, ostream* os) {
|
||||
UniversalPrintCharArray(begin, len, os);
|
||||
}
|
||||
|
||||
#ifdef __cpp_char8_t
|
||||
// Prints a (const) char8_t array of 'len' elements, starting at address
|
||||
// 'begin'.
|
||||
void UniversalPrintArray(const char8_t* begin, size_t len, ostream* os) {
|
||||
UniversalPrintCharArray(begin, len, os);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Prints a (const) char16_t array of 'len' elements, starting at address
|
||||
// 'begin'.
|
||||
void UniversalPrintArray(const char16_t* begin, size_t len, ostream* os) {
|
||||
UniversalPrintCharArray(begin, len, os);
|
||||
}
|
||||
|
||||
// Prints a (const) char32_t array of 'len' elements, starting at address
|
||||
// 'begin'.
|
||||
void UniversalPrintArray(const char32_t* begin, size_t len, ostream* os) {
|
||||
UniversalPrintCharArray(begin, len, os);
|
||||
}
|
||||
|
||||
// Prints a (const) wchar_t array of 'len' elements, starting at address
|
||||
// 'begin'.
|
||||
void UniversalPrintArray(const wchar_t* begin, size_t len, ostream* os) {
|
||||
UniversalPrintCharArray(begin, len, os);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Prints a null-terminated C-style string to the ostream.
|
||||
template <typename Char>
|
||||
void PrintCStringTo(const Char* s, ostream* os) {
|
||||
if (s == nullptr) {
|
||||
*os << "NULL";
|
||||
} else {
|
||||
*os << ImplicitCast_<const void*>(s) << " pointing to ";
|
||||
PrintCharsAsStringTo(s, std::char_traits<Char>::length(s), os);
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void PrintTo(const char* s, ostream* os) { PrintCStringTo(s, os); }
|
||||
|
||||
#ifdef __cpp_char8_t
|
||||
void PrintTo(const char8_t* s, ostream* os) { PrintCStringTo(s, os); }
|
||||
#endif
|
||||
|
||||
void PrintTo(const char16_t* s, ostream* os) { PrintCStringTo(s, os); }
|
||||
|
||||
void PrintTo(const char32_t* s, ostream* os) { PrintCStringTo(s, os); }
|
||||
|
||||
// MSVC compiler can be configured to define whar_t as a typedef
|
||||
// of unsigned short. Defining an overload for const wchar_t* in that case
|
||||
// would cause pointers to unsigned shorts be printed as wide strings,
|
||||
// possibly accessing more memory than intended and causing invalid
|
||||
// memory accesses. MSVC defines _NATIVE_WCHAR_T_DEFINED symbol when
|
||||
// wchar_t is implemented as a native type.
|
||||
#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED)
|
||||
// Prints the given wide C string to the ostream.
|
||||
void PrintTo(const wchar_t* s, ostream* os) { PrintCStringTo(s, os); }
|
||||
#endif // wchar_t is native
|
||||
|
||||
namespace {
|
||||
|
||||
bool ContainsUnprintableControlCodes(const char* str, size_t length) {
|
||||
const unsigned char *s = reinterpret_cast<const unsigned char *>(str);
|
||||
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
unsigned char ch = *s++;
|
||||
if (std::iscntrl(ch)) {
|
||||
switch (ch) {
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\r':
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsUTF8TrailByte(unsigned char t) { return 0x80 <= t && t<= 0xbf; }
|
||||
|
||||
bool IsValidUTF8(const char* str, size_t length) {
|
||||
const unsigned char *s = reinterpret_cast<const unsigned char *>(str);
|
||||
|
||||
for (size_t i = 0; i < length;) {
|
||||
unsigned char lead = s[i++];
|
||||
|
||||
if (lead <= 0x7f) {
|
||||
continue; // single-byte character (ASCII) 0..7F
|
||||
}
|
||||
if (lead < 0xc2) {
|
||||
return false; // trail byte or non-shortest form
|
||||
} else if (lead <= 0xdf && (i + 1) <= length && IsUTF8TrailByte(s[i])) {
|
||||
++i; // 2-byte character
|
||||
} else if (0xe0 <= lead && lead <= 0xef && (i + 2) <= length &&
|
||||
IsUTF8TrailByte(s[i]) &&
|
||||
IsUTF8TrailByte(s[i + 1]) &&
|
||||
// check for non-shortest form and surrogate
|
||||
(lead != 0xe0 || s[i] >= 0xa0) &&
|
||||
(lead != 0xed || s[i] < 0xa0)) {
|
||||
i += 2; // 3-byte character
|
||||
} else if (0xf0 <= lead && lead <= 0xf4 && (i + 3) <= length &&
|
||||
IsUTF8TrailByte(s[i]) &&
|
||||
IsUTF8TrailByte(s[i + 1]) &&
|
||||
IsUTF8TrailByte(s[i + 2]) &&
|
||||
// check for non-shortest form
|
||||
(lead != 0xf0 || s[i] >= 0x90) &&
|
||||
(lead != 0xf4 || s[i] < 0x90)) {
|
||||
i += 3; // 4-byte character
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConditionalPrintAsText(const char* str, size_t length, ostream* os) {
|
||||
if (!ContainsUnprintableControlCodes(str, length) &&
|
||||
IsValidUTF8(str, length)) {
|
||||
*os << "\n As Text: \"" << str << "\"";
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void PrintStringTo(const ::std::string& s, ostream* os) {
|
||||
if (PrintCharsAsStringTo(s.data(), s.size(), os) == kHexEscape) {
|
||||
if (GTEST_FLAG(print_utf8)) {
|
||||
ConditionalPrintAsText(s.data(), s.size(), os);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __cpp_char8_t
|
||||
void PrintU8StringTo(const ::std::u8string& s, ostream* os) {
|
||||
PrintCharsAsStringTo(s.data(), s.size(), os);
|
||||
}
|
||||
#endif
|
||||
|
||||
void PrintU16StringTo(const ::std::u16string& s, ostream* os) {
|
||||
PrintCharsAsStringTo(s.data(), s.size(), os);
|
||||
}
|
||||
|
||||
void PrintU32StringTo(const ::std::u32string& s, ostream* os) {
|
||||
PrintCharsAsStringTo(s.data(), s.size(), os);
|
||||
}
|
||||
|
||||
#if GTEST_HAS_STD_WSTRING
|
||||
void PrintWideStringTo(const ::std::wstring& s, ostream* os) {
|
||||
PrintCharsAsStringTo(s.data(), s.size(), os);
|
||||
}
|
||||
#endif // GTEST_HAS_STD_WSTRING
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace testing
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// The Google C++ Testing and Mocking Framework (Google Test)
|
||||
|
||||
#include "gtest/gtest-test-part.h"
|
||||
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
#include "src/gtest-internal-inl.h"
|
||||
|
||||
namespace testing {
|
||||
|
||||
using internal::GetUnitTestImpl;
|
||||
|
||||
// Gets the summary of the failure message by omitting the stack trace
|
||||
// in it.
|
||||
std::string TestPartResult::ExtractSummary(const char* message) {
|
||||
const char* const stack_trace = strstr(message, internal::kStackTraceMarker);
|
||||
return stack_trace == nullptr ? message : std::string(message, stack_trace);
|
||||
}
|
||||
|
||||
// Prints a TestPartResult object.
|
||||
std::ostream& operator<<(std::ostream& os, const TestPartResult& result) {
|
||||
return os << internal::FormatFileLocation(result.file_name(),
|
||||
result.line_number())
|
||||
<< " "
|
||||
<< (result.type() == TestPartResult::kSuccess
|
||||
? "Success"
|
||||
: result.type() == TestPartResult::kSkip
|
||||
? "Skipped"
|
||||
: result.type() == TestPartResult::kFatalFailure
|
||||
? "Fatal failure"
|
||||
: "Non-fatal failure")
|
||||
<< ":\n"
|
||||
<< result.message() << std::endl;
|
||||
}
|
||||
|
||||
// Appends a TestPartResult to the array.
|
||||
void TestPartResultArray::Append(const TestPartResult& result) {
|
||||
array_.push_back(result);
|
||||
}
|
||||
|
||||
// Returns the TestPartResult at the given index (0-based).
|
||||
const TestPartResult& TestPartResultArray::GetTestPartResult(int index) const {
|
||||
if (index < 0 || index >= size()) {
|
||||
printf("\nInvalid index (%d) into TestPartResultArray.\n", index);
|
||||
internal::posix::Abort();
|
||||
}
|
||||
|
||||
return array_[static_cast<size_t>(index)];
|
||||
}
|
||||
|
||||
// Returns the number of TestPartResult objects in the array.
|
||||
int TestPartResultArray::size() const {
|
||||
return static_cast<int>(array_.size());
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
HasNewFatalFailureHelper::HasNewFatalFailureHelper()
|
||||
: has_new_fatal_failure_(false),
|
||||
original_reporter_(GetUnitTestImpl()->
|
||||
GetTestPartResultReporterForCurrentThread()) {
|
||||
GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread(this);
|
||||
}
|
||||
|
||||
HasNewFatalFailureHelper::~HasNewFatalFailureHelper() {
|
||||
GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread(
|
||||
original_reporter_);
|
||||
}
|
||||
|
||||
void HasNewFatalFailureHelper::ReportTestPartResult(
|
||||
const TestPartResult& result) {
|
||||
if (result.fatally_failed())
|
||||
has_new_fatal_failure_ = true;
|
||||
original_reporter_->ReportTestPartResult(result);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace testing
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright 2008 Google Inc.
|
||||
// All Rights Reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
#include "gtest/gtest-typed-test.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace testing {
|
||||
namespace internal {
|
||||
|
||||
// Skips to the first non-space char in str. Returns an empty string if str
|
||||
// contains only whitespace characters.
|
||||
static const char* SkipSpaces(const char* str) {
|
||||
while (IsSpace(*str))
|
||||
str++;
|
||||
return str;
|
||||
}
|
||||
|
||||
static std::vector<std::string> SplitIntoTestNames(const char* src) {
|
||||
std::vector<std::string> name_vec;
|
||||
src = SkipSpaces(src);
|
||||
for (; src != nullptr; src = SkipComma(src)) {
|
||||
name_vec.push_back(StripTrailingSpaces(GetPrefixUntilComma(src)));
|
||||
}
|
||||
return name_vec;
|
||||
}
|
||||
|
||||
// Verifies that registered_tests match the test names in
|
||||
// registered_tests_; returns registered_tests if successful, or
|
||||
// aborts the program otherwise.
|
||||
const char* TypedTestSuitePState::VerifyRegisteredTestNames(
|
||||
const char* test_suite_name, const char* file, int line,
|
||||
const char* registered_tests) {
|
||||
RegisterTypeParameterizedTestSuite(test_suite_name, CodeLocation(file, line));
|
||||
|
||||
typedef RegisteredTestsMap::const_iterator RegisteredTestIter;
|
||||
registered_ = true;
|
||||
|
||||
std::vector<std::string> name_vec = SplitIntoTestNames(registered_tests);
|
||||
|
||||
Message errors;
|
||||
|
||||
std::set<std::string> tests;
|
||||
for (std::vector<std::string>::const_iterator name_it = name_vec.begin();
|
||||
name_it != name_vec.end(); ++name_it) {
|
||||
const std::string& name = *name_it;
|
||||
if (tests.count(name) != 0) {
|
||||
errors << "Test " << name << " is listed more than once.\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (registered_tests_.count(name) != 0) {
|
||||
tests.insert(name);
|
||||
} else {
|
||||
errors << "No test named " << name
|
||||
<< " can be found in this test suite.\n";
|
||||
}
|
||||
}
|
||||
|
||||
for (RegisteredTestIter it = registered_tests_.begin();
|
||||
it != registered_tests_.end();
|
||||
++it) {
|
||||
if (tests.count(it->first) == 0) {
|
||||
errors << "You forgot to list test " << it->first << ".\n";
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& errors_str = errors.GetString();
|
||||
if (errors_str != "") {
|
||||
fprintf(stderr, "%s %s", FormatFileLocation(file, line).c_str(),
|
||||
errors_str.c_str());
|
||||
fflush(stderr);
|
||||
posix::Abort();
|
||||
}
|
||||
|
||||
return registered_tests;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace testing
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
||||
// Copyright 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <cstdio>
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#if GTEST_OS_ESP8266 || GTEST_OS_ESP32
|
||||
#if GTEST_OS_ESP8266
|
||||
extern "C" {
|
||||
#endif
|
||||
void setup() {
|
||||
testing::InitGoogleTest();
|
||||
}
|
||||
|
||||
void loop() { RUN_ALL_TESTS(); }
|
||||
|
||||
#if GTEST_OS_ESP8266
|
||||
}
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
GTEST_API_ int main(int argc, char **argv) {
|
||||
printf("Running main() from %s\n", __FILE__);
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
#endif
|
||||
Executable
+220
@@ -0,0 +1,220 @@
|
||||
#! /bin/bash
|
||||
# -*- tab-width: 4; -*-
|
||||
# vi: set sw=2 ts=4 expandtab:
|
||||
|
||||
# Regression tests for ktx2check
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
#
|
||||
# Copyright 2010-2020 The Khronos Group, Inc.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
# Depth of this script relative to the project root
|
||||
depth=..
|
||||
|
||||
# Change dir to the testimages folder, a child of the script location...
|
||||
cd $(dirname $0)/testimages
|
||||
# ...and adjust depth
|
||||
depth=$depth/..
|
||||
|
||||
# Make paths relative to the testimages directory.
|
||||
ktx_root=$depth
|
||||
ktx2check_vs2013=$ktx_root/build/msvs/win/vs2013/x64/Release/ktx2check.exe
|
||||
ktx2check_vs2015=$ktx_root/build/msvs/win/vs2015/x64/Release/ktx2check.exe
|
||||
ktx2check_cmake=$ktx_root/build/cmake/linux/Release/ktx2check
|
||||
ktx2check_cmake_d=$ktx_root/build/cmake/linux/Debug/ktx2check
|
||||
ktx2check_make=$ktx_root/build/make/linux/out/Release/ktx2check
|
||||
ktx2check_make_d=$ktx_root/build/make/linux/out/Debug/ktx2check
|
||||
|
||||
declare -i numtests=0
|
||||
declare -i passed=0
|
||||
declare -i failed=0
|
||||
|
||||
if [ -n "$1" -a -x "$1" ]; then
|
||||
ktx2check="$1"
|
||||
elif [ -x "$ktx2check_vs2013" ]; then
|
||||
ktx2check=$ktx2check_vs2013
|
||||
elif [ -x "$ktx2check_vs2015" ]; then
|
||||
ktx2check=$ktx2check_vs2015
|
||||
elif [ -x "$ktx2check_cmake" ]; then
|
||||
ktx2check=$ktx2check_cmake
|
||||
elif [ -x "$ktx2check_cmake_d" ]; then
|
||||
ktx2check=$ktx2check_cmake_d
|
||||
elif [ -x "$ktx2check_make" ]; then
|
||||
ktx2check=$ktx2check_make
|
||||
elif [ -x "$ktx2check_make_d" ]; then
|
||||
ktx2check=$ktx2check_make_d
|
||||
elif ! ktx2check=$(which ktx2check); then
|
||||
echo $0: None of $ktx2check_vs2013, $ktx2check_vs2015, $ktx2check_gmake,
|
||||
echo $ktx2check_make_d, $ktx2check_cmake or $ktx2check_cmake_d found.
|
||||
echo $0: Aborting test
|
||||
exit 1
|
||||
fi
|
||||
|
||||
numtests=$numtests+1
|
||||
if $ktx2check --help 2> /dev/null; then
|
||||
passed=$passed+1
|
||||
else
|
||||
echo "--help not recognized"
|
||||
failed=$failed+1
|
||||
fi
|
||||
|
||||
numtests=$numtests+1
|
||||
if $ktx2check --version 2> /dev/null; then
|
||||
passed=$passed+1
|
||||
else
|
||||
echo "--version not recognized"
|
||||
failed=$failed+1
|
||||
fi
|
||||
|
||||
numtests=$numtests+1
|
||||
$ktx2check --foobar 2> /dev/null
|
||||
status=$?
|
||||
if [ $status -eq 1 ]; then
|
||||
passed=$passed+1
|
||||
elif [ $status -gt 1 ]; then
|
||||
echo "wrong exit code for invalid option."
|
||||
failed=$failed+1
|
||||
else
|
||||
echo "invalid option --foobar accepted"
|
||||
failed=$failed+1
|
||||
fi
|
||||
|
||||
#function cmpktx () {
|
||||
# if diff $1 $2; then
|
||||
# passed=$passed+1
|
||||
# rm $1
|
||||
# return 0
|
||||
# else
|
||||
# failed=$failed+1
|
||||
# echo "Created ktx file differs from target $2. Bad file saved in $PWD/$1"
|
||||
# return 1
|
||||
# fi
|
||||
#}
|
||||
|
||||
#function mktmp() {
|
||||
# # Git Bash does not include mktemp.
|
||||
# if which mktemp > /dev/null; then
|
||||
# mktemp toktx.XXXX
|
||||
# else
|
||||
# toktx.$numtests
|
||||
# fi
|
||||
#}
|
||||
|
||||
# Generate ktx file and compare with reference
|
||||
# gencmpktx <reference> <toktx args> <toktx infile> ...
|
||||
#function gencmpktx() {
|
||||
# numtests=$numtests+1
|
||||
# local args
|
||||
# local reference=$1; shift
|
||||
# local tempfile=$(mktmp)
|
||||
# for i in $*; do
|
||||
# if [ ${i:0:2} == "--" ]; then
|
||||
# args="$args $i"
|
||||
# shift
|
||||
# fi
|
||||
# done
|
||||
# #echo $toktx $args $tempfile $*
|
||||
# if $toktx $args $tempfile $*; then
|
||||
# cmpktx $tempfile $reference
|
||||
# fi
|
||||
#}
|
||||
|
||||
#---------------- Tests start here -----------------------
|
||||
|
||||
numtests=$numtests+1
|
||||
if $ktx2check *.ktx2; then
|
||||
passed=$passed+1
|
||||
else
|
||||
echo "Valid file(s) failed validation."
|
||||
failed=$failed+1
|
||||
fi
|
||||
|
||||
numtests=$numtests+1
|
||||
$ktx2check --quiet *.ktx2
|
||||
status=$?
|
||||
if [ $status -eq 0 ]; then
|
||||
if [ -z $output ]; then
|
||||
passed=$passed+1
|
||||
else
|
||||
echo "Output written with --quiet."
|
||||
failed=$failed+1
|
||||
fi
|
||||
elif [ $status -eq 1 ]; then
|
||||
echo "--quiet not recognized."
|
||||
failed=$failed+1
|
||||
else
|
||||
echo "Valid file(s) failed validation."
|
||||
failed=$failed+1
|
||||
fi
|
||||
|
||||
numtests=$numtests+1
|
||||
if ktx2check < color_grid_uastc_zstd.ktx2; then
|
||||
passed=$passed+1
|
||||
else
|
||||
echo "Valid file read via stdin failed validation."
|
||||
failed=$failed+1
|
||||
fi
|
||||
|
||||
numtests=$numtests+1
|
||||
if cat color_grid_uastc_zstd.ktx2 | ktx2check; then
|
||||
passed=$passed+1
|
||||
else
|
||||
echo "Valid file read via stdin failed validation."
|
||||
failed=$failed+1
|
||||
fi
|
||||
|
||||
numtests=$numtests+1
|
||||
cd ../badktx2
|
||||
if $ktx2check invalid_face_count.ktx2 > /dev/null; then
|
||||
echo "Invalid face count not spotted."
|
||||
failed=$failed+1
|
||||
else
|
||||
passed=$passed+1
|
||||
fi
|
||||
|
||||
numtests=$numtests+1
|
||||
output=$($ktx2check --quiet invalid_face_count.ktx2)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Invalid face count not spotted."
|
||||
failed=$failed+1
|
||||
else
|
||||
if [ -z $output ]; then
|
||||
passed=$passed+1
|
||||
else
|
||||
echo "Output written with --quiet."
|
||||
failed=$failed+1
|
||||
fi
|
||||
fi
|
||||
|
||||
numtests=$numtests+1
|
||||
cd ../badktx2
|
||||
if $ktx2check incorrect_mip_layout_and_padding.ktx2 > /dev/null; then
|
||||
echo "Invalid mip layout not spotted."
|
||||
failed=$failed+1
|
||||
else
|
||||
passed=$passed+1
|
||||
fi
|
||||
|
||||
numtests=$numtests+1
|
||||
output=$($ktx2check --quiet incorrect_mip_layout_and_padding.ktx2)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Invalid mip layout not spotted."
|
||||
failed=$failed+1
|
||||
else
|
||||
if [ -z $output ]; then
|
||||
passed=$passed+1
|
||||
else
|
||||
echo "Output written with --quiet."
|
||||
failed=$failed+1
|
||||
fi
|
||||
fi
|
||||
echo "Tests run: $numtests; passed: $passed; failed: $failed"
|
||||
if [ $failed -gt 0 ]; then
|
||||
exit 1;
|
||||
else
|
||||
exit 0;
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
# -*- tab-width: 4; -*-
|
||||
# vi: set sw=2 ts=4 expandtab:
|
||||
|
||||
# Copyright 2020 Andreas Atteneder
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
add_test( NAME ktx2check-test.help
|
||||
COMMAND ktx2check --help
|
||||
)
|
||||
set_tests_properties(
|
||||
ktx2check-test.help
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "^Usage: ktx2check"
|
||||
)
|
||||
|
||||
add_test( NAME ktx2check-test.version
|
||||
COMMAND ktx2check --version
|
||||
)
|
||||
set_tests_properties(
|
||||
ktx2check-test.version
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "^ktx2check v[0-9][0-9\\.]+"
|
||||
)
|
||||
|
||||
# Why are there <test> and matching <test>-exit-code tests
|
||||
#
|
||||
# A "limitation", i.e. a bug or misfeature, in ctest is that it tests
|
||||
# REGULAR_EXPRESSION match OR exit code. For WILL_FAIL tests, failure is
|
||||
# recognized by first attempting to match the FAIL_REGULAR_EXPRESSION
|
||||
# and then checking the exit code. Thus if the app's exit-code is non-zero
|
||||
# the test will fail regardless of whether the match succeeds. When the
|
||||
# match succeeds ctests notes this in the LastTest.log. Nothing is noted
|
||||
# about a failed match. Similarly for non WILL_FAIL tests, ctest tests
|
||||
# PASS_REGULAR_EXPRESSION match OR exit code == 0.
|
||||
#
|
||||
# When failures occur all the KTX tools BOTH output a message, which
|
||||
# we want to verify in these tests, and exit with a non-zero error
|
||||
# code, which we also want to verify. We need a different test for
|
||||
# each case. Perversely to test for the intended failure message we have
|
||||
# to use a non WILL_FAIL test with a PASS_REGULAR_EXPRESSION so that
|
||||
# when the PASS RE fails to match we are guaranteed the exit code
|
||||
# test will fail. When it does match the exit code is not checked.
|
||||
|
||||
add_test( NAME ktx2check-test.foobar
|
||||
COMMAND ktx2check --foobar
|
||||
)
|
||||
set_tests_properties(
|
||||
ktx2check-test.foobar
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "^Usage: ktx2check"
|
||||
)
|
||||
add_test( NAME ktx2check-test.foobar-exit-code
|
||||
COMMAND ktx2check --foobar
|
||||
)
|
||||
set_tests_properties(
|
||||
ktx2check-test.foobar-exit-code
|
||||
PROPERTIES
|
||||
WILL_FAIL TRUE
|
||||
)
|
||||
|
||||
add_test( NAME ktx2check-test.fail-when-last-file-valid
|
||||
COMMAND ktx2check ../badktx2/bad_typesize.ktx2 astc_ldr_6x6_arraytex_7.ktx2
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/testimages
|
||||
)
|
||||
set_tests_properties(
|
||||
ktx2check-test.fail-when-last-file-valid
|
||||
PROPERTIES
|
||||
WILL_FAIL TRUE
|
||||
)
|
||||
|
||||
add_test( NAME ktx2check-test.all
|
||||
# Invoke via sh workaround, since CMake puts asterisk in quotes
|
||||
# otherwise ( "*.ktx2" )
|
||||
COMMAND ${BASH_EXECUTABLE} -c "$<TARGET_FILE:ktx2check> *.ktx2"
|
||||
COMMAND_EXPAND_LISTS
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/testimages
|
||||
)
|
||||
|
||||
add_test( NAME ktx2check-test.all-quiet
|
||||
# Invoke via sh workaround, since CMake puts asterisk in quotes
|
||||
# otherwise ( "*.ktx2" )
|
||||
COMMAND ${BASH_EXECUTABLE} -c "$<TARGET_FILE:ktx2check> --quiet *.ktx2"
|
||||
COMMAND_EXPAND_LISTS
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/testimages
|
||||
)
|
||||
set_tests_properties(
|
||||
ktx2check-test.all-quiet
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "^$"
|
||||
)
|
||||
|
||||
add_test( NAME ktx2check-test.stdin-read
|
||||
COMMAND ${BASH_EXECUTABLE} -c "$<TARGET_FILE:ktx2check> < color_grid_uastc_zstd.ktx2
|
||||
"
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/testimages
|
||||
)
|
||||
|
||||
add_test( NAME ktx2check-test.pipe-read
|
||||
COMMAND ${BASH_EXECUTABLE} -c "cat color_grid_uastc_zstd.ktx2 | $<TARGET_FILE:ktx2check>"
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/testimages
|
||||
)
|
||||
|
||||
add_test( NAME ktx2check-test.invalid-face-count-and-padding
|
||||
COMMAND ktx2check invalid_face_count_and_padding.ktx2
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/badktx2
|
||||
)
|
||||
|
||||
add_test( NAME ktx2check-test.invalid-face-count-and-padding-quiet
|
||||
COMMAND ktx2check --quiet invalid_face_count_and_padding.ktx2
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/badktx2
|
||||
)
|
||||
add_test( NAME ktx2check-test.invalid-face-count-and-padding-quiet-exit-code
|
||||
COMMAND ktx2check --quiet invalid_face_count_and_padding.ktx2
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/badktx2
|
||||
)
|
||||
|
||||
add_test( NAME ktx2check-test.incorrect-mip-layout-and-padding
|
||||
COMMAND ktx2check incorrect_mip_layout_and_padding.ktx2
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/badktx2
|
||||
)
|
||||
|
||||
add_test( NAME ktx2check-test.incorrect-mip-layout-and-padding-quiet
|
||||
COMMAND ktx2check --quiet incorrect_mip_layout_and_padding.ktx2
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/badktx2
|
||||
)
|
||||
add_test( NAME ktx2check-test.incorrect-mip-layout-and-padding-quiet-exit-code
|
||||
COMMAND ktx2check incorrect_mip_layout_and_padding.ktx2
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/badktx2
|
||||
)
|
||||
|
||||
add_test( NAME ktx2check-test.bad-typesize
|
||||
COMMAND ktx2check bad_typesize.ktx2
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/badktx2
|
||||
)
|
||||
set_tests_properties(
|
||||
ktx2check-test.bad-typesize
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "ERROR: typeSize, 1, does not match data described by the DFD."
|
||||
)
|
||||
add_test( NAME ktx2check-test.bad-typesize-exit-code
|
||||
COMMAND ktx2check bad_typesize.ktx2
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/badktx2
|
||||
)
|
||||
|
||||
add_test( NAME ktx2check-test.no-nul-on-value
|
||||
COMMAND ktx2check no_nul_on_kvd_val.ktx2
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/badktx2
|
||||
)
|
||||
set_tests_properties(
|
||||
ktx2check-test.no-nul-on-value
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "WARNING: KTXswizzle value missing encouraged NUL termination."
|
||||
)
|
||||
add_test( NAME ktx2check-test.no-nul-on-value-exit-code
|
||||
COMMAND ktx2check no_nul_on_kvd_val.ktx2
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/badktx2
|
||||
)
|
||||
add_test( NAME ktx2check-test.no-nul-on-value-warn-as-error-exit-code
|
||||
COMMAND ktx2check -w no_nul_on_kvd_val.ktx2
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/badktx2
|
||||
)
|
||||
|
||||
set_tests_properties(
|
||||
ktx2check-test.invalid-face-count-and-padding-quiet
|
||||
ktx2check-test.incorrect-mip-layout-and-padding-quiet
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "^$"
|
||||
)
|
||||
|
||||
set_tests_properties(
|
||||
ktx2check-test.bad-typesize-exit-code
|
||||
ktx2check-test.invalid-face-count-and-padding
|
||||
ktx2check-test.invalid-face-count-and-padding-quiet-exit-code
|
||||
ktx2check-test.incorrect-mip-layout-and-padding
|
||||
ktx2check-test.incorrect-mip-layout-and-padding-quiet-exit-code
|
||||
ktx2check-test.no-nul-on-value-warn-as-error-exit-code
|
||||
PROPERTIES
|
||||
WILL_FAIL TRUE
|
||||
)
|
||||
@@ -0,0 +1,120 @@
|
||||
# -*- tab-width: 4; -*-
|
||||
# vi: set sw=2 ts=4 expandtab:
|
||||
|
||||
# Copyright 2022 Mark Callow
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
add_test( NAME ktx2ktx2-test.help
|
||||
COMMAND ktx2ktx2 --help
|
||||
)
|
||||
set_tests_properties(
|
||||
ktx2ktx2-test.help
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "^Usage: ktx2ktx2"
|
||||
)
|
||||
|
||||
add_test( NAME ktx2ktx2-test.version
|
||||
COMMAND ktx2ktx2 --version
|
||||
)
|
||||
set_tests_properties(
|
||||
ktx2ktx2-test.version
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "^ktx2ktx2 v[0-9][0-9\\.]+"
|
||||
)
|
||||
|
||||
# Why are there <test> and matching <test>-exit-code tests
|
||||
#
|
||||
# See comment under the same title in ./ktx2check-tests.cmake.
|
||||
|
||||
add_test( NAME ktx2ktx2-test.foobar
|
||||
COMMAND ktx2ktx2 --foobar
|
||||
)
|
||||
set_tests_properties(
|
||||
ktx2ktx2-test.foobar
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "^Usage: ktx2ktx2"
|
||||
)
|
||||
add_test( NAME ktx2ktx2-test.foobar-exit-code
|
||||
COMMAND ktx2ktx2 --foobar
|
||||
)
|
||||
set_tests_properties(
|
||||
ktx2ktx2-test.foobar-exit-code
|
||||
PROPERTIES
|
||||
WILL_FAIL TRUE
|
||||
)
|
||||
|
||||
add_test( NAME ktx2ktx2-test.many-in-one-out
|
||||
COMMAND ktx2ktx2 -o foo a.ktx b.ktx c.ktx
|
||||
)
|
||||
set_tests_properties(
|
||||
ktx2ktx2-test.many-in-one-out
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "^Can't use -o when there are multiple infiles."
|
||||
)
|
||||
|
||||
add_test( NAME ktx2ktx2-test.many-in-one-out-exit-code
|
||||
COMMAND ktx2ktx2 -o foo a.ktx b.ktx c.ktx
|
||||
)
|
||||
set_tests_properties(
|
||||
ktx2ktx2-test.many-in-one-out-exit-code
|
||||
PROPERTIES
|
||||
WILL_FAIL TRUE
|
||||
)
|
||||
|
||||
set( IMG_DIR "${CMAKE_CURRENT_SOURCE_DIR}/testimages" )
|
||||
|
||||
add_test( NAME ktx2ktx2-test.ktx2-in
|
||||
COMMAND ktx2ktx2 -o foo CesiumLogoFlat.ktx2
|
||||
WORKING_DIRECTORY ${IMG_DIR}
|
||||
)
|
||||
set_tests_properties(
|
||||
ktx2ktx2-test.ktx2-in
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION ".* is not a KTX v1 file."
|
||||
)
|
||||
add_test( NAME ktx2ktx2-test.ktx2-in-exit-code
|
||||
COMMAND ktx2ktx2 -o foo CesiumLogoFlat.ktx2
|
||||
WORKING_DIRECTORY ${IMG_DIR}
|
||||
)
|
||||
set_tests_properties(
|
||||
ktx2ktx2-test.ktx2-in-exit-code
|
||||
PROPERTIES
|
||||
WILL_FAIL TRUE
|
||||
)
|
||||
|
||||
function( cnvrtcmpktx test_name reference source args )
|
||||
set( workfile ktx2ktx2.${reference} )
|
||||
add_test( NAME ktx2ktx2-test.cnvrt-${test_name}
|
||||
COMMAND ${BASH_EXECUTABLE} -c "$<TARGET_FILE:ktx2ktx2> --test ${args} -o ${workfile} ${source} && diff ${reference} ${workfile} && rm ${workfile}"
|
||||
WORKING_DIRECTORY ${IMG_DIR}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
function( cnvrtcmpktx_implied_out test_name base_source args )
|
||||
set( source ${base_source}.ktx )
|
||||
set( reference ${base_source}.ktx2 )
|
||||
set( worksource ktx2ktx2.ip.${source} )
|
||||
set( workfile ktx2ktx2.ip.${reference} )
|
||||
add_test( NAME ktx2ktx2-test.cnvrt-implied-out-${test_name}
|
||||
COMMAND ${BASH_EXECUTABLE} -c "cp ${source} ${worksource} && $<TARGET_FILE:ktx2ktx2> --test ${args} ${worksource} && rm ${worksource} && diff ${reference} ${workfile} && rm ${workfile}"
|
||||
WORKING_DIRECTORY ${IMG_DIR}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
cnvrtcmpktx( 2d-uncompressed orient-down-metadata-u.ktx2 orient-down-metadata.ktx "-f" )
|
||||
cnvrtcmpktx( 2d-bc2 pattern_02_bc2.ktx2 pattern_02_bc2.ktx "-f" )
|
||||
cnvrtcmpktx( 2d-array-astc texturearray_astc_8x8_unorm.ktx2 texturearray_astc_8x8_unorm.ktx "-f" )
|
||||
|
||||
cnvrtcmpktx_implied_out( 2d-bc2 pattern_02_bc2 "-f" )
|
||||
|
||||
cnvrtcmpktx( unicode-file-hu hűtő.ktx2 hűtő.ktx "-f" )
|
||||
cnvrtcmpktx( unicode-file-jp テクスチャ.ktx2 テクスチャ.ktx "-f" )
|
||||
cnvrtcmpktx( unicode-file-ar نَسِيج.ktx2 نَسِيج.ktx "-f" )
|
||||
cnvrtcmpktx( unicode-file-zh 质地.ktx2 质地.ktx "-f" )
|
||||
cnvrtcmpktx( unicode-file-ko 조직.ktx2 조직.ktx "-f" )
|
||||
|
||||
cnvrtcmpktx_implied_out( unicode-file-hu hűtő "-f" )
|
||||
cnvrtcmpktx_implied_out( unicode-file-jp テクスチャ "-f" )
|
||||
cnvrtcmpktx_implied_out( unicode-file-ar نَسِيج "-f" )
|
||||
cnvrtcmpktx_implied_out( unicode-file-zh 质地 "-f" )
|
||||
cnvrtcmpktx_implied_out( unicode-file-ko 조직 "-f" )
|
||||
@@ -0,0 +1,47 @@
|
||||
# Copyright 2022-2023 The Khronos Group Inc.
|
||||
# Copyright 2022-2023 RasterGrid Kft.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
|
||||
add_executable(ktxdiff
|
||||
ktxdiff_main.cpp
|
||||
)
|
||||
|
||||
target_compile_features(
|
||||
ktxdiff
|
||||
PUBLIC
|
||||
cxx_std_17
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
ktxdiff
|
||||
PRIVATE
|
||||
.
|
||||
$<TARGET_PROPERTY:ktx,INCLUDE_DIRECTORIES>
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
ktxdiff
|
||||
SYSTEM
|
||||
PRIVATE
|
||||
$<TARGET_PROPERTY:objUtil,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
${PROJECT_SOURCE_DIR}/lib
|
||||
${PROJECT_SOURCE_DIR}/other_include
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
ktxdiff
|
||||
PRIVATE
|
||||
ktx
|
||||
${ASTCENC_LIB_TARGET}
|
||||
fmt::fmt
|
||||
objUtil
|
||||
)
|
||||
|
||||
target_compile_definitions(
|
||||
ktxdiff
|
||||
PRIVATE
|
||||
$<TARGET_PROPERTY:ktx,INTERFACE_COMPILE_DEFINITIONS>
|
||||
)
|
||||
|
||||
set_test_properties(ktxdiff)
|
||||
@@ -0,0 +1,461 @@
|
||||
// Copyright 2022-2023 The Khronos Group Inc.
|
||||
// Copyright 2022-2023 RasterGrid Kft.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "ktx.h"
|
||||
#include "ktxint.h"
|
||||
#include "texture2.h"
|
||||
#include "vkformat_enum.h"
|
||||
#include "platform_utils.h"
|
||||
|
||||
#include "astc-encoder/Source/astcenc.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/os.h>
|
||||
#include <fmt/ostream.h>
|
||||
#include <fmt/printf.h>
|
||||
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] constexpr inline T ceil_div(const T x, const T y) noexcept {
|
||||
assert(y != 0);
|
||||
return (x + y - 1) / y;
|
||||
}
|
||||
|
||||
// C++20 - std::bit_cast
|
||||
template <class To, class From>
|
||||
[[nodiscard]] constexpr inline To bit_cast(const From& src) noexcept {
|
||||
static_assert(sizeof(To) == sizeof(From));
|
||||
static_assert(std::is_trivially_copyable_v<From>);
|
||||
static_assert(std::is_trivially_copyable_v<To>);
|
||||
static_assert(std::is_trivially_constructible_v<To>);
|
||||
To dst;
|
||||
std::memcpy(&dst, &src, sizeof(To));
|
||||
return dst;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr inline bool isFormatAstc(VkFormat format) noexcept {
|
||||
switch (format) {
|
||||
case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_12x12_UNORM_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_3x3x3_SFLOAT_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_4x3x3_SFLOAT_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_4x4x3_SFLOAT_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_4x4x4_SFLOAT_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_5x4x4_SFLOAT_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_5x5x4_SFLOAT_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_5x5x5_SFLOAT_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_6x5x5_SFLOAT_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_6x6x5_SFLOAT_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT: [[fallthrough]];
|
||||
case VK_FORMAT_ASTC_6x6x6_SFLOAT_BLOCK_EXT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
int EXIT_CODE_ERROR = 2;
|
||||
int EXIT_CODE_MISMATCH = 1;
|
||||
int EXIT_CODE_MATCH = 0;
|
||||
|
||||
template <typename... Args>
|
||||
void error(int return_code, Args&&... args) {
|
||||
fmt::print(std::cerr, std::forward<Args>(args)...);
|
||||
std::exit(return_code);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::string errnoMessage() {
|
||||
return std::make_error_code(static_cast<std::errc>(errno)).message();
|
||||
}
|
||||
|
||||
struct Texture {
|
||||
std::string filepath;
|
||||
std::vector<std::byte> rawData;
|
||||
|
||||
KTX_header2 header;
|
||||
std::vector<ktxLevelIndexEntry> levelIndices;
|
||||
const std::byte* levelIndexData = nullptr;
|
||||
size_t levelIndexSize = 0;
|
||||
const std::byte* dfdData = nullptr;
|
||||
size_t dfdSize = 0;
|
||||
const std::byte* kvdData = nullptr;
|
||||
size_t kvdSize = 0;
|
||||
const std::byte* sgdData = nullptr;
|
||||
size_t sgdSize = 0;
|
||||
|
||||
ktxTexture2* handle = nullptr;
|
||||
bool transcoded = false;
|
||||
|
||||
public:
|
||||
explicit Texture(std::string filepath) :
|
||||
filepath(filepath) {
|
||||
std::memset(&header, 0, sizeof(header));
|
||||
|
||||
loadFile();
|
||||
loadKTX();
|
||||
loadMetadata();
|
||||
}
|
||||
~Texture() {
|
||||
std::free(handle);
|
||||
}
|
||||
void loadFile();
|
||||
void loadKTX();
|
||||
void loadMetadata();
|
||||
inline ktxTexture2* operator->() const {
|
||||
return handle;
|
||||
}
|
||||
};
|
||||
|
||||
void Texture::loadFile() {
|
||||
auto file = std::ifstream(DecodeUTF8Path(filepath).c_str(), std::ios::binary | std::ios::in | std::ios::ate);
|
||||
if (!file)
|
||||
error(EXIT_CODE_ERROR, "ktxdiff error \"{}\": Failed to open file: {}\n", filepath, errnoMessage());
|
||||
|
||||
const auto fileSize = file.tellg();
|
||||
file.seekg(0);
|
||||
if (file.fail())
|
||||
error(EXIT_CODE_ERROR, "ktxdiff error \"{}\": Failed to seek file: {}\n", filepath, errnoMessage());
|
||||
|
||||
rawData.resize(fileSize);
|
||||
file.read(reinterpret_cast<char*>(rawData.data()), fileSize);
|
||||
if (file.fail())
|
||||
error(EXIT_CODE_ERROR, "ktxdiff error \"{}\": Failed to read file: {}\n", filepath, errnoMessage());
|
||||
}
|
||||
|
||||
void Texture::loadKTX() {
|
||||
KTX_error_code ec = KTX_SUCCESS;
|
||||
ec = ktxTexture2_CreateFromMemory(
|
||||
reinterpret_cast<const ktx_uint8_t*>(rawData.data()),
|
||||
rawData.size(),
|
||||
KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
|
||||
&handle);
|
||||
if (ec != KTX_SUCCESS)
|
||||
error(EXIT_CODE_ERROR, "ktxdiff error \"{}\": ktxTexture2_CreateFromNamedFile: {}\n", filepath, ktxErrorString(ec));
|
||||
|
||||
if (ktxTexture2_NeedsTranscoding(handle)) {
|
||||
ec = ktxTexture2_TranscodeBasis(handle, KTX_TTF_RGBA32, 0);
|
||||
if (ec != KTX_SUCCESS)
|
||||
error(EXIT_CODE_ERROR, "ktxdiff error \"{}\": ktxTexture2_TranscodeBasis: {}\n", filepath, ktxErrorString(ec));
|
||||
transcoded = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Texture::loadMetadata() {
|
||||
const auto headerData = rawData.data();
|
||||
const auto headerSize = sizeof(KTX_header2);
|
||||
std::memcpy(&header, headerData, headerSize);
|
||||
|
||||
const auto numLevels = std::max(header.levelCount, 1u);
|
||||
levelIndexData = rawData.data() + sizeof(KTX_header2);
|
||||
levelIndexSize = sizeof(ktxLevelIndexEntry) * numLevels;
|
||||
levelIndices.resize(numLevels);
|
||||
std::memcpy(levelIndices.data(), levelIndexData, levelIndexSize);
|
||||
|
||||
if (header.dataFormatDescriptor.byteLength != 0) {
|
||||
dfdData = rawData.data() + header.dataFormatDescriptor.byteOffset;
|
||||
dfdSize = header.dataFormatDescriptor.byteLength;
|
||||
}
|
||||
if (header.keyValueData.byteLength != 0) {
|
||||
kvdData = rawData.data() + header.keyValueData.byteOffset;
|
||||
kvdSize = header.keyValueData.byteLength;
|
||||
}
|
||||
if (header.supercompressionGlobalData.byteLength != 0) {
|
||||
sgdData = rawData.data() + header.dataFormatDescriptor.byteOffset;
|
||||
sgdSize = header.dataFormatDescriptor.byteLength;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
struct CompareResult {
|
||||
bool match = true;
|
||||
float difference = 0.f;
|
||||
std::size_t elementIndex = 0;
|
||||
std::size_t byteOffset = 0;
|
||||
};
|
||||
|
||||
CompareResult compareUnorm8(const char* rawLhs, const char* rawRhs, std::size_t rawSize, float tolerance) {
|
||||
const auto* lhs = reinterpret_cast<const uint8_t*>(rawLhs);
|
||||
const auto* rhs = reinterpret_cast<const uint8_t*>(rawRhs);
|
||||
const auto element_size = sizeof(uint8_t);
|
||||
const auto count = rawSize / element_size;
|
||||
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
const auto diff = std::abs(static_cast<float>(lhs[i]) / 255.f - static_cast<float>(rhs[i]) / 255.f);
|
||||
if (diff > tolerance)
|
||||
return CompareResult{false, diff, i, i * element_size};
|
||||
}
|
||||
|
||||
return CompareResult{};
|
||||
}
|
||||
|
||||
CompareResult compareSFloat32(const char* rawLhs, const char* rawRhs, std::size_t rawSize, float tolerance) {
|
||||
const auto* lhs = reinterpret_cast<const float*>(rawLhs);
|
||||
const auto* rhs = reinterpret_cast<const float*>(rawRhs);
|
||||
const auto element_size = sizeof(float);
|
||||
const auto count = rawSize / element_size;
|
||||
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
const auto diff = std::abs(lhs[i] - rhs[i]);
|
||||
if (diff > tolerance)
|
||||
return CompareResult{false, diff, i, i * element_size};
|
||||
}
|
||||
|
||||
return CompareResult{};
|
||||
}
|
||||
|
||||
auto decodeASTC(const char* compressedData, std::size_t compressedSize, uint32_t width, uint32_t height,
|
||||
const std::string& filepath, bool isFormatSRGB, uint32_t blockSizeX, uint32_t blockSizeY, uint32_t blockSizeZ) {
|
||||
|
||||
const auto threadCount = 1u;
|
||||
static constexpr astcenc_swizzle swizzle{ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A};
|
||||
|
||||
astcenc_error ec = ASTCENC_SUCCESS;
|
||||
|
||||
const astcenc_profile profile = isFormatSRGB ? ASTCENC_PRF_LDR_SRGB : ASTCENC_PRF_LDR;
|
||||
astcenc_config config{};
|
||||
ec = astcenc_config_init(profile, blockSizeX, blockSizeY, blockSizeZ, ASTCENC_PRE_MEDIUM, ASTCENC_FLG_DECOMPRESS_ONLY, &config);
|
||||
if (ec != ASTCENC_SUCCESS)
|
||||
error(EXIT_CODE_ERROR, "ktxdiff error \"{}\": astcenc_config_init: {}\n", filepath, astcenc_get_error_string(ec));
|
||||
|
||||
struct ASTCencStruct {
|
||||
astcenc_context* context = nullptr;
|
||||
~ASTCencStruct() {
|
||||
astcenc_context_free(context);
|
||||
}
|
||||
} astcenc;
|
||||
astcenc_context*& context = astcenc.context;
|
||||
|
||||
ec = astcenc_context_alloc(&config, threadCount, &context);
|
||||
if (ec != ASTCENC_SUCCESS)
|
||||
error(EXIT_CODE_ERROR, "ktxdiff error \"{}\": astcenc_context_alloc: {}\n", filepath, astcenc_get_error_string(ec));
|
||||
|
||||
astcenc_image image{};
|
||||
image.dim_x = width;
|
||||
image.dim_y = height;
|
||||
image.dim_z = 1; // 3D ASTC formats are currently not supported
|
||||
const auto uncompressedSize = width * height * 4 * sizeof(uint8_t);
|
||||
auto uncompressedBuffer = std::make_unique<uint8_t[]>(uncompressedSize);
|
||||
auto* bufferPtr = uncompressedBuffer.get();
|
||||
image.data = reinterpret_cast<void**>(&bufferPtr);
|
||||
image.data_type = ASTCENC_TYPE_U8;
|
||||
|
||||
ec = astcenc_decompress_image(context, reinterpret_cast<const uint8_t*>(compressedData), compressedSize, &image, &swizzle, 0);
|
||||
if (ec != ASTCENC_SUCCESS)
|
||||
error(EXIT_CODE_ERROR, "ktxdiff error \"{}\": astcenc_decompress_image: {}\n", filepath, astcenc_get_error_string(ec));
|
||||
|
||||
astcenc_decompress_reset(context);
|
||||
|
||||
struct Result {
|
||||
std::unique_ptr<uint8_t[]> data;
|
||||
std::size_t size;
|
||||
};
|
||||
return Result{std::move(uncompressedBuffer), uncompressedSize};
|
||||
}
|
||||
|
||||
CompareResult compareAstc(const char* lhs, const char* rhs, std::size_t size, uint32_t width, uint32_t height,
|
||||
const std::string& filepathLhs, const std::string& filepathRhs,
|
||||
bool isFormatSRGB, uint32_t blockSizeX, uint32_t blockSizeY, uint32_t blockSizeZ,
|
||||
float tolerance) {
|
||||
const auto uncompressedLhs = decodeASTC(lhs, size, width, height, filepathLhs, isFormatSRGB, blockSizeX, blockSizeY, blockSizeZ);
|
||||
const auto uncompressedRhs = decodeASTC(rhs, size, width, height, filepathRhs, isFormatSRGB, blockSizeX, blockSizeY, blockSizeZ);
|
||||
|
||||
return compareUnorm8(
|
||||
reinterpret_cast<const char*>(uncompressedLhs.data.get()),
|
||||
reinterpret_cast<const char*>(uncompressedRhs.data.get()),
|
||||
uncompressedLhs.size,
|
||||
tolerance);
|
||||
}
|
||||
|
||||
bool compare(Texture& lhs, Texture& rhs, float tolerance) {
|
||||
const auto vkFormat = static_cast<VkFormat>(lhs.header.vkFormat);
|
||||
const auto* bdfd = reinterpret_cast<const uint32_t*>(lhs.dfdData) + 1;
|
||||
const auto componentCount = KHR_DFDSAMPLECOUNT(bdfd);
|
||||
const auto texelBlockDimension0 = static_cast<uint8_t>(KHR_DFDVAL(bdfd, TEXELBLOCKDIMENSION0));
|
||||
const auto texelBlockDimension1 = static_cast<uint8_t>(KHR_DFDVAL(bdfd, TEXELBLOCKDIMENSION1));
|
||||
const auto texelBlockDimension2 = static_cast<uint8_t>(KHR_DFDVAL(bdfd, TEXELBLOCKDIMENSION2));
|
||||
const auto blockSizeX = texelBlockDimension0 + 1u;
|
||||
const auto blockSizeY = texelBlockDimension1 + 1u;
|
||||
const auto blockSizeZ = texelBlockDimension2 + 1u;
|
||||
const bool isFormatSRGB = KHR_DFDVAL(bdfd, TRANSFER) == KHR_DF_TRANSFER_SRGB;
|
||||
|
||||
const bool isSigned = (KHR_DFDSVAL(bdfd, 0, QUALIFIERS) & KHR_DF_SAMPLE_DATATYPE_SIGNED) != 0;
|
||||
const bool isFloat = (KHR_DFDSVAL(bdfd, 0, QUALIFIERS) & KHR_DF_SAMPLE_DATATYPE_FLOAT) != 0;
|
||||
const bool isNormalized = KHR_DFDSVAL(bdfd, 0, SAMPLEUPPER) == (isFloat ? bit_cast<uint32_t>(1.0f) : 1u);
|
||||
const bool is32Bit = KHR_DFDSVAL(bdfd, 0, BITLENGTH) + 1 == 32;
|
||||
const bool is8Bit = KHR_DFDSVAL(bdfd, 0, BITLENGTH) + 1 == 8;
|
||||
const bool isFormatSFloat32 = isSigned && isFloat && is32Bit && vkFormat != VK_FORMAT_D32_SFLOAT_S8_UINT;
|
||||
const bool isFormatUNORM8 = !isSigned && !isFloat && is8Bit && isNormalized;
|
||||
|
||||
const auto mismatch = [&](auto&&... args) {
|
||||
fmt::print("ktxdiff: ");
|
||||
fmt::print(std::forward<decltype(args)>(args)...);
|
||||
fmt::print(" between\n");
|
||||
fmt::print(" Expected: {} and\n", lhs.filepath);
|
||||
fmt::print(" Received: {}\n", rhs.filepath);
|
||||
return false;
|
||||
};
|
||||
|
||||
if (lhs.transcoded) {
|
||||
// For encoded images the compressed data sizes can differ.
|
||||
// Skip the related checks for header.supercompressionGlobalData and levelIndex
|
||||
if (std::memcmp(&lhs.header, &rhs.header, sizeof(lhs.header) - sizeof(ktxIndexEntry64)) != 0)
|
||||
return mismatch("Mismatching header");
|
||||
} else {
|
||||
if (std::memcmp(&lhs.header, &rhs.header, sizeof(lhs.header)) != 0)
|
||||
return mismatch("Mismatching header");
|
||||
if (lhs.levelIndexSize != rhs.levelIndexSize)
|
||||
return mismatch("Mismatching levelIndices");
|
||||
for (uint32_t i = 0; i < lhs.levelIndices.size(); ++i)
|
||||
// Offsets and (compressed) sizes can differ, but uncompressedByteLength must match
|
||||
if (lhs.levelIndices[i].uncompressedByteLength != rhs.levelIndices[i].uncompressedByteLength)
|
||||
return mismatch("Mismatching levelIndices[{}].uncompressedByteLength", i);
|
||||
}
|
||||
if (lhs.dfdSize != rhs.dfdSize || std::memcmp(lhs.dfdData, rhs.dfdData, lhs.dfdSize) != 0)
|
||||
return mismatch("Mismatching DFD");
|
||||
|
||||
if (lhs.kvdSize != rhs.kvdSize || std::memcmp(lhs.kvdData, rhs.kvdData, lhs.kvdSize) != 0)
|
||||
return mismatch("Mismatching KVD");
|
||||
|
||||
if (!lhs.transcoded)
|
||||
if (lhs.sgdSize != rhs.sgdSize || std::memcmp(lhs.sgdData, rhs.sgdData, lhs.sgdSize) != 0)
|
||||
return mismatch("Mismatching SGD");
|
||||
|
||||
// If the tolerance is 1 or above accept every image data as matching
|
||||
if (tolerance >= 1.0f)
|
||||
return true;
|
||||
|
||||
for (uint32_t levelIndex = 0; levelIndex < lhs->numLevels; ++levelIndex) {
|
||||
const auto imageSize = ktxTexture_GetImageSize(ktxTexture(lhs.handle), levelIndex);
|
||||
const auto imageWidth = std::max(1u, lhs->baseWidth >> levelIndex);
|
||||
const auto imageHeight = std::max(1u, lhs->baseHeight >> levelIndex);
|
||||
const auto imageDepth = std::max(1u, lhs->baseDepth >> levelIndex);
|
||||
|
||||
for (uint32_t faceIndex = 0; faceIndex < lhs->numFaces; ++faceIndex) {
|
||||
for (uint32_t layerIndex = 0; layerIndex < lhs->numLayers; ++layerIndex) {
|
||||
for (uint32_t depthIndex = 0; depthIndex < ceil_div(imageDepth, blockSizeZ); ++depthIndex) {
|
||||
|
||||
ktx_size_t imageOffset;
|
||||
ktxTexture2_GetImageOffset(lhs.handle, levelIndex, layerIndex, faceIndex + depthIndex, &imageOffset);
|
||||
const char* imageDataLhs = reinterpret_cast<const char*>(lhs->pData) + imageOffset;
|
||||
const char* imageDataRhs = reinterpret_cast<const char*>(rhs->pData) + imageOffset;
|
||||
|
||||
CompareResult result;
|
||||
if (lhs.transcoded || isFormatUNORM8) {
|
||||
result = compareUnorm8(imageDataLhs, imageDataRhs, imageSize, tolerance);
|
||||
} else if (isFormatAstc(vkFormat)) {
|
||||
result = compareAstc(imageDataLhs, imageDataRhs, imageSize, imageWidth, imageHeight,
|
||||
lhs.filepath, rhs.filepath,
|
||||
isFormatSRGB, blockSizeX, blockSizeY, blockSizeZ,
|
||||
tolerance);
|
||||
} else if (isFormatSFloat32) {
|
||||
result = compareSFloat32(imageDataLhs, imageDataRhs, imageSize, tolerance);
|
||||
} else {
|
||||
for (std::size_t i = 0; i < imageSize; ++i) {
|
||||
if (imageDataLhs[i] != imageDataRhs[i])
|
||||
return mismatch("Mismatching image data: level {}, face {}, layer {}, depth {}, image byte {}",
|
||||
levelIndex, faceIndex, layerIndex, depthIndex, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.match) {
|
||||
return mismatch("Mismatching image data (diff: {}): level {}, face {}, layer {}, depth {}, pixel {}, component {}",
|
||||
result.difference, levelIndex, faceIndex, layerIndex, depthIndex,
|
||||
result.elementIndex / componentCount, result.elementIndex % componentCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// EXIT CODES:
|
||||
/// 0 - Matching files
|
||||
/// 1 - Mismatching files
|
||||
/// 2 - Error while loading, decoding or processing an input file
|
||||
int main(int argc, char* argv[]) {
|
||||
InitUTF8CLI(argc, argv);
|
||||
|
||||
if (argc < 3) {
|
||||
fmt::print("Missing input file arguments\n");
|
||||
fmt::print("Usage: ktxdiff <expected-ktx2> <received-ktx2> [tolerance]\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
const float tolerance = argc > 3 ? std::stof(argv[3]) : 0.05f;
|
||||
|
||||
Texture lhs(argv[1]);
|
||||
Texture rhs(argv[2]);
|
||||
const auto match = compare(lhs, rhs, tolerance);
|
||||
|
||||
return match ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
# -*- tab-width: 4; -*-
|
||||
# vi: set sw=2 ts=4 expandtab:
|
||||
|
||||
# Copyright 2022 Mark Callow
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# toktx shares a common scapp class with ktxsc so the toktx tests suffice
|
||||
# for testing actual compression.
|
||||
|
||||
add_test( NAME ktxsc-test.help
|
||||
COMMAND ktxsc --help
|
||||
)
|
||||
set_tests_properties(
|
||||
ktxsc-test.help
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "^Usage: ktxsc"
|
||||
)
|
||||
|
||||
add_test( NAME ktxsc-test.version
|
||||
COMMAND ktxsc --version
|
||||
)
|
||||
set_tests_properties(
|
||||
ktxsc-test.version
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "^ktxsc v[0-9][0-9\\.]+"
|
||||
)
|
||||
|
||||
# Why are there <test> and matching <test>-exit-code tests
|
||||
#
|
||||
# See comment under the same title in ./ktx2check-tests.cmake.
|
||||
|
||||
add_test( NAME ktxsc-test.foobar
|
||||
COMMAND ktxsc --foobar
|
||||
)
|
||||
set_tests_properties(
|
||||
ktxsc-test.foobar
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "^Usage: ktxsc"
|
||||
)
|
||||
add_test( NAME ktxsc-test.foobar-exit-code
|
||||
COMMAND ktxsc --foobar
|
||||
)
|
||||
set_tests_properties(
|
||||
ktxsc-test.foobar-exit-code
|
||||
PROPERTIES
|
||||
WILL_FAIL TRUE
|
||||
)
|
||||
|
||||
add_test( NAME ktxsc-test.many-in-one-out
|
||||
COMMAND ktxsc -o foo a.ktx2 b.ktx2 c.ktx2
|
||||
)
|
||||
set_tests_properties(
|
||||
ktxsc-test.many-in-one-out
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "^Can't use -o when there are multiple infiles."
|
||||
)
|
||||
|
||||
add_test( NAME ktxsc-test.many-in-one-out-exit-code
|
||||
COMMAND ktxsc -o foo a.ktx2 b.ktx2 c.ktx2
|
||||
)
|
||||
set_tests_properties(
|
||||
ktxsc-test.many-in-one-out-exit-code
|
||||
PROPERTIES
|
||||
WILL_FAIL TRUE
|
||||
)
|
||||
|
||||
set( IMG_DIR "${CMAKE_CURRENT_SOURCE_DIR}/testimages" )
|
||||
|
||||
add_test( NAME ktxsc-test.ktx1-in
|
||||
COMMAND ktxsc --zcmp 5 -o foo orient-up-metadata.ktx
|
||||
WORKING_DIRECTORY "${IMG_DIR}"
|
||||
)
|
||||
set_tests_properties(
|
||||
ktxsc-test.ktx1-in
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION ".* is not a KTX v2 file."
|
||||
)
|
||||
add_test( NAME ktxsc-test.ktx1-in-exit-code
|
||||
COMMAND ktxsc --zcmp 5 -o foo orient-up-metadata.ktx
|
||||
WORKING_DIRECTORY ${IMG_DIR}
|
||||
)
|
||||
set_tests_properties(
|
||||
ktxsc-test.ktx1-in-exit-code
|
||||
PROPERTIES
|
||||
WILL_FAIL TRUE
|
||||
)
|
||||
|
||||
function( sccmpktx test_name reference source args )
|
||||
set( workfile ktxsc.${reference} )
|
||||
add_test( NAME ktxsc-test.${test_name}
|
||||
COMMAND ${BASH_EXECUTABLE} -c "$<TARGET_FILE:ktxsc> --test ${args} -o ${workfile} ${source} && diff ${reference} ${workfile} && rm ${workfile}"
|
||||
WORKING_DIRECTORY ${IMG_DIR}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
function( sccmpktxinplacecurdir test_name reference source args )
|
||||
set( workfile ktxsc.ip1.${reference} )
|
||||
add_test( NAME ktxsc-test.inplace-curdir-${test_name}
|
||||
COMMAND ${BASH_EXECUTABLE} -c "cp ${source} ${workfile} && $<TARGET_FILE:ktxsc> --test ${args} ${workfile} && diff ${reference} ${workfile} && rm ${workfile}"
|
||||
WORKING_DIRECTORY ${IMG_DIR}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
function( sccmpktxinplacediffdir test_name reference source args )
|
||||
set( workfile ktxsc.ip2.${reference} )
|
||||
add_test( NAME ktxsc-test.inplace-diffdir-${test_name}
|
||||
COMMAND ${BASH_EXECUTABLE} -c "cp ${source} ${workfile} && pushd ../.. && $<TARGET_FILE:ktxsc> --test ${args} ${IMG_DIR}/${workfile} && popd && diff ${reference} ${workfile} && rm ${workfile}"
|
||||
WORKING_DIRECTORY ${IMG_DIR}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
sccmpktx( zcmp-cubemap skybox_zstd.ktx2 skybox.ktx2 "--zcmp 5" )
|
||||
sccmpktxinplacecurdir( zcmp-cubemap skybox_zstd.ktx2 skybox.ktx2 "--zcmp 5" )
|
||||
sccmpktxinplacediffdir( zcmp_cubemap skybox_zstd.ktx2 skybox.ktx2 "--zcmp 5" )
|
||||
sccmpktxinplacecurdir( unicode-file-hu hűtő_zstd.ktx2 hűtő.ktx2 "--zcmp 5" )
|
||||
sccmpktxinplacecurdir( unicode-file-jp テクスチャ_zstd.ktx2 テクスチャ.ktx2 "--zcmp 5" )
|
||||
sccmpktxinplacecurdir( unicode-file-ar نَسِيج_zstd.ktx2 نَسِيج.ktx2 "--zcmp 5" )
|
||||
sccmpktxinplacecurdir( unicode-file-zh 质地_zstd.ktx2 质地.ktx2 "--zcmp 5" )
|
||||
sccmpktxinplacecurdir( unicode-file-ko 조직_zstd.ktx2 조직.ktx2 "--zcmp 5" )
|
||||
sccmpktxinplacediffdir( unicode-file-hu hűtő_zstd.ktx2 hűtő.ktx2 "--zcmp 5" )
|
||||
sccmpktxinplacediffdir( unicode-file-jp テクスチャ_zstd.ktx2 テクスチャ.ktx2 "--zcmp 5" )
|
||||
sccmpktxinplacediffdir( unicode-file-ar نَسِيج_zstd.ktx2 نَسِيج.ktx2 "--zcmp 5" )
|
||||
sccmpktxinplacediffdir( unicode-file-zh 质地_zstd.ktx2 质地.ktx2 "--zcmp 5" )
|
||||
sccmpktxinplacediffdir( unicode-file-ko 조직_zstd.ktx2 조직.ktx2 "--zcmp 5" )
|
||||
@@ -0,0 +1,270 @@
|
||||
# Copyright 2017-2020 The Khronos Group Inc.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#if(WIN32)
|
||||
# cmake_print_variables(
|
||||
# CMAKE_SYSTEM_VERSION
|
||||
# CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION
|
||||
# )
|
||||
#endif()
|
||||
|
||||
# N.B. Do not set CMAKE_XCODE_GENERATE_SCHEME. If set, CMake will
|
||||
# destroy the scheme, losing any user-made settings, when generating
|
||||
# the project after the cache has been deleted. Cache deletion is
|
||||
# needed whenever Xcode is updated with new SDK versions due to CMake
|
||||
# hard-wiring the version in many of its variables.
|
||||
#
|
||||
# XCODE_SCHEME settings can be found at
|
||||
# https://cmake.org/cmake/help/latest/prop_tgt/XCODE_GENERATE_SCHEME.html
|
||||
# These settings are recommended by MoltenVK Runtime User Guide.
|
||||
set( CMAKE_XCODE_SCHEME_ENABLE_GPU_API_VALIDATION FALSE )
|
||||
set( CMAKE_XCODE_SCHEME_ENABLE_GPU_FRAME_CAPTURE_MODE DISABLED )
|
||||
|
||||
#set(CMAKE_FIND_DEBUG_MODE TRUE)
|
||||
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
|
||||
#set(CMAKE_FIND_DEBUG_MODE FALSE)
|
||||
if(NOT EMSCRIPTEN)
|
||||
# There is no official assimp port for Emscripten, and we've had
|
||||
# no time to experiment, so tests that use assimp are omitted from
|
||||
# loadtests when building for the web.
|
||||
find_package(assimp REQUIRED CONFIG REQUIRED)
|
||||
endif()
|
||||
|
||||
# We use our own local copy of GL headers to ensure we have glcorearb.h.
|
||||
set( OPENGL_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/other_include )
|
||||
|
||||
if(APPLE AND IOS)
|
||||
if( CMAKE_OSX_ARCHITECTURES )
|
||||
list(LENGTH CMAKE_OSX_ARCHITECTURES archs_len)
|
||||
list(GET CMAKE_OSX_ARCHITECTURES 0 arch0)
|
||||
if( ${archs_len} GREATER 1 OR (NOT ${arch0} STREQUAL "arm64") )
|
||||
message(FATAL_ERROR "iOS loadtests only supported on arm64."
|
||||
" Please disable KTX_FEATURE_LOADTEST_APPS"
|
||||
" or change CMAKE_OSX_ARCHITECTURES.")
|
||||
endif()
|
||||
endif()
|
||||
# Find Frameworks
|
||||
find_library(AudioToolbox_LIBRARY AudioToolbox)
|
||||
find_library(AVFoundation_LIBRARY AVFoundation)
|
||||
find_library(CoreAudio_LIBRARY CoreAudio)
|
||||
find_library(CoreBluetooth_LIBRARY CoreBluetooth)
|
||||
find_library(CoreGraphics_LIBRARY CoreGraphics)
|
||||
find_library(CoreHaptics_LIBRARY CoreHaptics)
|
||||
find_library(CoreMotion_LIBRARY CoreMotion)
|
||||
find_library(Foundation_LIBRARY Foundation)
|
||||
find_library(GameController_LIBRARY GameController)
|
||||
find_library(IOSurface_LIBRARY IOSurface)
|
||||
if(${KTX_FEATURE_LOADTEST_APPS} MATCHES "Vulkan")
|
||||
find_library(Metal_LIBRARY Metal)
|
||||
endif()
|
||||
if(${KTX_FEATURE_LOADTEST_APPS} MATCHES "OpenGL")
|
||||
find_library(OpenGLES_LIBRARY OpenGLES)
|
||||
endif()
|
||||
find_library(QuartzCore_LIBRARY QuartzCore)
|
||||
find_library(UIKit_LIBRARY UIKit)
|
||||
endif()
|
||||
|
||||
function( ensure_runtime_dependencies_windows target )
|
||||
# Custom copy commands to ensure all dependencies (testimages,
|
||||
# shaders, models) are in correct location relative to executable.
|
||||
|
||||
if(${target} MATCHES "vkloadtests")
|
||||
add_custom_command( TARGET ${target} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_BINARY_DIR}/shaders" "$<TARGET_FILE_DIR:${target}>/resources"
|
||||
COMMENT "Copy shaders to build destination"
|
||||
)
|
||||
endif()
|
||||
add_custom_command( TARGET ${target} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${PROJECT_SOURCE_DIR}/tests/testimages" "$<TARGET_FILE_DIR:${target}>/resources"
|
||||
COMMENT "Copy testimages to build destination"
|
||||
)
|
||||
add_custom_command( TARGET ${target} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${PROJECT_SOURCE_DIR}/tests/loadtests/common/models" "$<TARGET_FILE_DIR:${target}>/resources"
|
||||
COMMENT "Copy models to build destination"
|
||||
)
|
||||
endfunction()
|
||||
|
||||
add_library( appfwSDL STATIC
|
||||
appfwSDL/AppBaseSDL.cpp
|
||||
appfwSDL/AppBaseSDL.h
|
||||
appfwSDL/main.cpp
|
||||
common/LoadTestSample.cpp
|
||||
common/LoadTestSample.h
|
||||
common/ltexceptions.h
|
||||
common/SwipeDetector.cpp
|
||||
common/SwipeDetector.h
|
||||
common/disable_glm_warnings.h
|
||||
common/reenable_warnings.h
|
||||
common/vecmath.hpp
|
||||
geom/cube_data.h
|
||||
geom/cube.h
|
||||
geom/frame.h
|
||||
geom/quad.h
|
||||
${PROJECT_SOURCE_DIR}/external/SDL_gesture/SDL_gesture.h
|
||||
)
|
||||
|
||||
target_compile_features(appfwSDL PUBLIC c_std_99 cxx_std_17)
|
||||
if(EMSCRIPTEN)
|
||||
target_compile_options( appfwSDL PUBLIC
|
||||
"SHELL:--use-port=sdl3"
|
||||
)
|
||||
endif()
|
||||
|
||||
set_target_properties(appfwSDL PROPERTIES
|
||||
CXX_VISIBILITY_PRESET ${STATIC_APP_LIB_SYMBOL_VISIBILITY}
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
appfwSDL
|
||||
PUBLIC
|
||||
appfwSDL
|
||||
$<TARGET_PROPERTY:ktx,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
${PROJECT_SOURCE_DIR}/utils
|
||||
${PROJECT_SOURCE_DIR}/external/SDL_gesture
|
||||
common
|
||||
geom
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
appfwSDL
|
||||
SYSTEM AFTER PUBLIC
|
||||
SDL3::Headers
|
||||
${PROJECT_SOURCE_DIR}/other_include # For glm
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
appfwSDL
|
||||
PUBLIC
|
||||
SDL3::SDL3
|
||||
)
|
||||
|
||||
if(${KTX_FEATURE_LOADTEST_APPS} MATCHES "OpenGL")
|
||||
add_library( GLAppSDL STATIC
|
||||
appfwSDL/GLAppSDL.cpp
|
||||
appfwSDL/GLAppSDL.h
|
||||
glloadtests/GLLoadTests.cpp
|
||||
glloadtests/GLLoadTests.h
|
||||
)
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
target_compile_options( GLAppSDL PUBLIC
|
||||
"SHELL:-s DISABLE_EXCEPTION_CATCHING=0"
|
||||
"SHELL:--use-port=sdl3"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(GLAppSDL appfwSDL)
|
||||
|
||||
target_include_directories(
|
||||
GLAppSDL
|
||||
PUBLIC
|
||||
$<TARGET_PROPERTY:appfwSDL,INCLUDE_DIRECTORIES>
|
||||
glloadtests
|
||||
glloadtests/utils
|
||||
)
|
||||
|
||||
# The above appfwSDL include brings with it the
|
||||
# INTERFACE_SYSTEM_INCLUDE_DIRECTORIES.
|
||||
target_include_directories(
|
||||
GLAppSDL
|
||||
SYSTEM BEFORE PUBLIC
|
||||
$<IF:$<BOOL:${WIN32}>,${GLEW_INCLUDE_DIR},${OPENGL_INCLUDE_DIR}>
|
||||
#$<TARGET_PROPERTY:appfwSDL,INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>
|
||||
)
|
||||
|
||||
set_target_properties(GLAppSDL PROPERTIES
|
||||
CXX_VISIBILITY_PRESET ${STATIC_APP_LIB_SYMBOL_VISIBILITY}
|
||||
)
|
||||
endif()
|
||||
|
||||
set( LOAD_TEST_COMMON_LIBS )
|
||||
|
||||
if(APPLE)
|
||||
set( EXE_FLAG MACOSX_BUNDLE )
|
||||
set( KTX_APP_ICON_BASENAME ktx_app)
|
||||
set( KTX_DOC_ICON_BASENAME ktx_document)
|
||||
set( KTX_APP_ICON ${KTX_APP_ICON_BASENAME}.icns)
|
||||
set( KTX_DOC_ICON ${KTX_DOC_ICON_BASENAME}.icns)
|
||||
# On iOS an icon is a directory of images not a single file.
|
||||
# BASENAME is the name of the directory in the common .xcassets directory.
|
||||
# Assets are copied to the bundle in the build target setup.
|
||||
set( KTX_APP_ICON_PATH ${PROJECT_SOURCE_DIR}/icons/mac/${KTX_APP_ICON} )
|
||||
set( KTX_DOC_ICON_PATH ${PROJECT_SOURCE_DIR}/icons/mac/${KTX_DOC_ICON} )
|
||||
cmake_print_variables(assimp_LIBRARIES)
|
||||
if(IOS)
|
||||
set( LOAD_TEST_COMMON_LIBS
|
||||
assimp::assimp
|
||||
)
|
||||
else()
|
||||
set( LOAD_TEST_COMMON_LIBS
|
||||
assimp::assimp
|
||||
)
|
||||
endif()
|
||||
elseif(LINUX)
|
||||
set( KTX_APP_ICON_PATH ${PROJECT_SOURCE_DIR}/icons/linux/ktx_app.svg )
|
||||
set( LOAD_TEST_COMMON_LIBS
|
||||
assimp::assimp
|
||||
#${SDL3_LIBRARIES}
|
||||
SDL3::SDL3
|
||||
)
|
||||
elseif(WIN32)
|
||||
set( EXE_FLAG WIN32 )
|
||||
set( KTX_APP_ICON_PATH ${PROJECT_SOURCE_DIR}/icons/win/ktx_app.ico )
|
||||
set( LOAD_TEST_COMMON_LIBS
|
||||
assimp::assimp
|
||||
)
|
||||
endif()
|
||||
|
||||
set( LOAD_TEST_COLLADA_MODELS
|
||||
"teapot.dae"
|
||||
)
|
||||
list(TRANSFORM LOAD_TEST_COLLADA_MODELS PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/common/models/")
|
||||
# An Xcode (or possibly CMake) update in late 2024 or early 2025 started
|
||||
# compressing .dae files when copying them to bundle resources. This piece
|
||||
# of undocumented magic makes it just copy. The Xcode GUI presents an option
|
||||
# menu on the file with "Copy" and "Compress" in the project settings browser.
|
||||
# This attribute sets it to "Copy."
|
||||
set_source_files_properties( ${LOAD_TEST_COLLADA_MODELS}
|
||||
PROPERTIES
|
||||
XCODE_FILE_ATTRIBUTES "--decompress"
|
||||
)
|
||||
|
||||
set( LOAD_TEST_OBJ_MODELS
|
||||
"cube.obj"
|
||||
"sphere.obj"
|
||||
"torusknot.obj"
|
||||
)
|
||||
list(TRANSFORM LOAD_TEST_OBJ_MODELS PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/common/models/")
|
||||
# Hack to prevent *.obj 3D files being mistaken as linkable obj files
|
||||
set_source_files_properties( ${LOAD_TEST_OBJ_MODELS}
|
||||
PROPERTIES HEADER_FILE_ONLY TRUE
|
||||
)
|
||||
set( LOAD_TEST_COMMON_MODELS
|
||||
${LOAD_TEST_COLLADA_MODELS}
|
||||
${LOAD_TEST_OBJ_MODELS}
|
||||
)
|
||||
|
||||
# A custom loadtest_models target does not work as configure fails at
|
||||
# an install_target that uses this with an error "the target is not
|
||||
# an executable, library or module."
|
||||
#add_custom_target( loadtest_models
|
||||
# SOURCES ${LOAD_TEST_COMMON_MODELS}
|
||||
#)
|
||||
|
||||
set( LOAD_TEST_COMMON_RESOURCE_FILES
|
||||
${KTX_APP_ICON_PATH}
|
||||
${KTX_DOC_ICON_PATH}
|
||||
${LOAD_TEST_COMMON_MODELS}
|
||||
)
|
||||
|
||||
if(LINUX)
|
||||
set( LOAD_TEST_DESTROOT "/opt" )
|
||||
endif()
|
||||
|
||||
if(${KTX_FEATURE_LOADTEST_APPS} MATCHES "OpenGL")
|
||||
include(glloadtests.cmake)
|
||||
endif()
|
||||
|
||||
if(${KTX_FEATURE_LOADTEST_APPS} MATCHES "Vulkan")
|
||||
include(vkloadtests.cmake)
|
||||
endif()
|
||||
@@ -0,0 +1,138 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/* $Id: f63e0a9e6eed51ed84a8eea1eff0708c8a6af22b $ */
|
||||
|
||||
/*
|
||||
* Copyright 2015-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @file
|
||||
* @~English
|
||||
*
|
||||
* @brief Base class for SDL applications.
|
||||
*/
|
||||
|
||||
#include "AppBaseSDL.h"
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
bool
|
||||
AppBaseSDL::initialize(Args& /*args*/)
|
||||
{
|
||||
const char* basePath = SDL_GetBasePath();
|
||||
// SDL_GetBasePath returns directory of the app, except for
|
||||
// iOS & macOS app bundles where it returns the Resources
|
||||
// directory.
|
||||
if (basePath == NULL)
|
||||
basePath = SDL_strdup("./");
|
||||
sBasePath = basePath;
|
||||
#if SDL_PLATFORM_LINUX
|
||||
// TODO figure out best way to handle these resources
|
||||
sBasePath += "../resources/";
|
||||
#endif
|
||||
#if SDL_PLATFORM_WINDOWS
|
||||
// Ditto
|
||||
sBasePath += "resources/";
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AppBaseSDL::initializeFPSTimer()
|
||||
{
|
||||
lastFrameTime = 0;
|
||||
fpsCounter.numFrames = 0;
|
||||
fpsCounter.lastFPS = 0;
|
||||
startTicks = fpsCounter.startTicks = SDL_GetPerformanceCounter();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AppBaseSDL::finalize() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
AppBaseSDL::doEvent(SDL_Event* event)
|
||||
{
|
||||
switch (event->type) {
|
||||
case SDL_EVENT_QUIT:
|
||||
finalize();
|
||||
exit(0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AppBaseSDL::onFPSUpdate()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Protected, non-virtual
|
||||
void
|
||||
AppBaseSDL::drawFrame()
|
||||
{
|
||||
ticks_t ticks = SDL_GetPerformanceCounter();
|
||||
Uint64 tps = SDL_GetPerformanceFrequency();
|
||||
uint32_t msTicksSinceStart = (uint32_t)((ticks - startTicks) * 1000 / tps);
|
||||
|
||||
drawFrame(msTicksSinceStart);
|
||||
|
||||
ticks_t endTicks = SDL_GetPerformanceCounter();
|
||||
lastFrameTime = (1000.0f * (endTicks - ticks)) / tps;
|
||||
fpsCounter.numFrames++;
|
||||
if (endTicks - fpsCounter.startTicks > tps) {
|
||||
fpsCounter.lastFPS = (float)(fpsCounter.numFrames * tps)
|
||||
/ (endTicks - fpsCounter.startTicks);
|
||||
onFPSUpdate(); // Notify listeners that fps value has been updated.
|
||||
fpsCounter.startTicks = endTicks;
|
||||
fpsCounter.numFrames = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Window title and text overlay functions
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
|
||||
void
|
||||
AppBaseSDL::setAppTitle(const char* const szExtra)
|
||||
{
|
||||
appTitle = name();
|
||||
if (szExtra != NULL && szExtra[0] != '\0') {
|
||||
appTitle += ": ";
|
||||
appTitle += szExtra;
|
||||
}
|
||||
setWindowTitle();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AppBaseSDL::setWindowTitle()
|
||||
{
|
||||
std::stringstream ss;
|
||||
std::string wt;
|
||||
|
||||
ss << std::fixed << std::setprecision(2)
|
||||
<< lastFrameTime << "ms (" << fpsCounter.lastFPS << " fps)" << " ";
|
||||
wt = ss.str();
|
||||
wt += appTitle;
|
||||
SDL_SetWindowTitle(pswMainWindow, wt.c_str());
|
||||
}
|
||||
|
||||
|
||||
const char* appName()
|
||||
{
|
||||
return theApp->name();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
#ifndef APP_BASE_SDL_H_1456211087
|
||||
#define APP_BASE_SDL_H_1456211087
|
||||
|
||||
/* $Id: f63e0a9e6eed51ed84a8eea1eff0708c8a6af22b $ */
|
||||
|
||||
/*
|
||||
* Copyright 2015-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @file
|
||||
* @~English
|
||||
*
|
||||
* @brief Declarations for App framework using SDL.
|
||||
*/
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class AppBaseSDL {
|
||||
public:
|
||||
typedef Uint64 ticks_t;
|
||||
typedef std::vector<std::string> Args;
|
||||
AppBaseSDL(const char* const name) : szName(name), appTitle(name) { }
|
||||
virtual bool initialize(Args& args);
|
||||
virtual void finalize();
|
||||
// Ticks in milliseconds since start.
|
||||
virtual void drawFrame(uint32_t) { }
|
||||
// When used with SDL_SetEventWatch, return value is ignored. When used
|
||||
// with SDL_SetEventFilter, 1 causes event to be added to SDL's internal
|
||||
// event queue, 0 causes it to be dropped.
|
||||
virtual bool doEvent(SDL_Event* event);
|
||||
virtual void onFPSUpdate();
|
||||
virtual SDL_Window* getMainWindow() { return pswMainWindow; }
|
||||
|
||||
void drawFrame();
|
||||
void initializeFPSTimer();
|
||||
const char* name() { return szName; }
|
||||
const std::string getAssetPath() { return sBasePath; }
|
||||
|
||||
// Sets title to be used on window title bar. Content of szExtra ia
|
||||
// appended to the app name.
|
||||
virtual void setAppTitle(const char* const szExtra);
|
||||
|
||||
static bool onEvent(void* userdata, SDL_Event* event) {
|
||||
return ((AppBaseSDL *)userdata)->doEvent(event);
|
||||
}
|
||||
|
||||
static void onDrawFrame(void* userdata) {
|
||||
((AppBaseSDL *)userdata)->drawFrame();
|
||||
}
|
||||
|
||||
protected:
|
||||
// Sets text on window title bar. Fps value is preprended to appTitle.
|
||||
virtual void setWindowTitle();
|
||||
|
||||
ticks_t startTicks;
|
||||
float lastFrameTime; // ms
|
||||
struct fpsCounter {
|
||||
ticks_t startTicks;
|
||||
int numFrames;
|
||||
float lastFPS;
|
||||
} fpsCounter;
|
||||
|
||||
SDL_Window* pswMainWindow;
|
||||
|
||||
const char* const szName;
|
||||
std::string appTitle;
|
||||
std::string sBasePath;
|
||||
|
||||
};
|
||||
|
||||
extern class AppBaseSDL* theApp;
|
||||
|
||||
#endif /* APP_BASE_SDL_H_1456211087 */
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/* $Id: ac63511da134f2c25a9e1da86a36bc27b6198ae3 $ */
|
||||
|
||||
/*
|
||||
* Copyright 2015-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @file
|
||||
* @~English
|
||||
*
|
||||
* @brief GLAppSDL app class.
|
||||
*/
|
||||
|
||||
#if defined(_WIN32)
|
||||
#if _MSC_VER < 1900
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include "windows.h"
|
||||
#include "GL/glew.h"
|
||||
#include "SDL3/SDL_loadso.h"
|
||||
#else
|
||||
#define GL_GLEXT_PROTOTYPES 1
|
||||
#include "GL/glcorearb.h" // for glEnable and FRAMEBUFFER_RGB
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include "GLAppSDL.h"
|
||||
|
||||
#if SDL_PLATFORM_WINDOWS
|
||||
void setWindowsIcon(SDL_Window *sdlWindow);
|
||||
#endif
|
||||
|
||||
bool
|
||||
GLAppSDL::initialize(Args& args)
|
||||
{
|
||||
if (!AppBaseSDL::initialize(args))
|
||||
return false;
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minorVersion);
|
||||
// On SDL3 this defaults to 8. On SDL2 it was 0.
|
||||
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
|
||||
#if !defined(EMSCRIPTEN)
|
||||
if (majorVersion >= 3)
|
||||
SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, 1);
|
||||
#endif
|
||||
#if defined(DEBUG) && !defined(EMSCRIPTEN)
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
|
||||
#endif
|
||||
|
||||
if (profile == SDL_GL_CONTEXT_PROFILE_ES) {
|
||||
#if 0
|
||||
int numVideoDrivers = SDL_GetNumVideoDrivers();
|
||||
int i;
|
||||
const char** drivers;
|
||||
|
||||
drivers = (const char**)SDL_malloc(sizeof(const char*) * numVideoDrivers);
|
||||
for (i = 0; i < numVideoDrivers; i++) {
|
||||
drivers[i] = SDL_GetVideoDriver(i);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Only the indicated platforms pay attention to these hints
|
||||
// but they could be set on any platform.
|
||||
#if SDL_PLATFORM_WINDOWS || SDL_PLATFORM_LINUX
|
||||
SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1");
|
||||
#endif
|
||||
|
||||
#if SDL_PLATFORM_WINDOWS
|
||||
// If using ANGLE copied from Chrome should set to "d3dcompiler_46.dll"
|
||||
// Should set value via compiler -D definition from gyp file.
|
||||
SDL_SetHint(SDL_HINT_VIDEO_WIN_D3DCOMPILER, "none");
|
||||
#endif
|
||||
}
|
||||
|
||||
SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1");
|
||||
// CAUTION: Setting this to 0 (the default) on macOS causes loss of all touch events
|
||||
// from a trackpad not just those corresponding to mouse clicks.
|
||||
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "1");
|
||||
|
||||
#if 0
|
||||
const char* mt = SDL_GetHint(SDL_HINT_MOUSE_TOUCH_EVENTS);
|
||||
SDL_Log("MOUSE_TOUCH_EVENTS = %s", mt);
|
||||
const char* tm = SDL_GetHint(SDL_HINT_TOUCH_MOUSE_EVENTS);
|
||||
SDL_Log("TOUCH_MOUSE_EVENTS = %s", tm);
|
||||
const char* tto = SDL_GetHint(SDL_HINT_TRACKPAD_IS_TOUCH_ONLY);
|
||||
SDL_Log("TRACKPAD_IS_TOUCH_ONLY = %s", tto);
|
||||
#endif
|
||||
|
||||
pswMainWindow = SDL_CreateWindow(
|
||||
szName,
|
||||
w_width, w_height,
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE
|
||||
);
|
||||
|
||||
if (pswMainWindow == NULL) {
|
||||
(void)SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, szName, SDL_GetError(), NULL);
|
||||
return false;
|
||||
}
|
||||
#if SDL_PLATFORM_WINDOWS
|
||||
// Set the application's own icon in place of the Windows default set by SDL.
|
||||
// Needs to be done here to avoid change being visible.
|
||||
setWindowsIcon(pswMainWindow);
|
||||
#endif
|
||||
|
||||
sgcGLContext = SDL_GL_CreateContext(pswMainWindow);
|
||||
// Work around bug in SDL. It returns a 2.x context when 3.x is requested.
|
||||
// It does though internally record an error.
|
||||
const char* error = SDL_GetError();
|
||||
if (sgcGLContext == NULL
|
||||
|| (error[0] != '\0'
|
||||
&& majorVersion >= 3
|
||||
&& (profile == SDL_GL_CONTEXT_PROFILE_CORE
|
||||
|| profile == SDL_GL_CONTEXT_PROFILE_COMPATIBILITY))
|
||||
) {
|
||||
(void)SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, szName, SDL_GetError(), NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if SDL_PLATFORM_WINDOWS
|
||||
if (profile != SDL_GL_CONTEXT_PROFILE_ES)
|
||||
{
|
||||
// No choice but to use GLEW for GL on Windows; there is no .lib with static
|
||||
// bindings. For ES we use one of the hardware vendor SDKs all of which have
|
||||
// static bindings.
|
||||
// TODO: Figure out how to support {GLX,WGL}_EXT_create_context_es2_profile
|
||||
// were there are no static bindings. Need a GLEW equivalent for ES and
|
||||
// different compile options. Perhaps can borrow function loading stuff
|
||||
// from SDL's testgles2.c.
|
||||
|
||||
// So one build of this library can be linked in to applications using GLEW and
|
||||
// applications not using GLEW, do not call any GLEW functions directly.
|
||||
// Call via queried function pointers.
|
||||
SDL_SharedObject* glewdll;
|
||||
#if defined(_DEBUG)
|
||||
glewdll = SDL_LoadObject("glew32d.dll");
|
||||
// KTX-Software repo only contains non-debug library for x64 hence this.
|
||||
if (glewdll == NULL) {
|
||||
glewdll = SDL_LoadObject("glew32.dll");
|
||||
}
|
||||
#else
|
||||
glewdll = SDL_LoadObject("glew32.dll");
|
||||
#endif
|
||||
if (glewdll == NULL) {
|
||||
std::string sName(szName);
|
||||
|
||||
(void)SDL_ShowSimpleMessageBox(
|
||||
SDL_MESSAGEBOX_ERROR,
|
||||
szName,
|
||||
SDL_GetError(),
|
||||
NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef GLenum(GLEWAPIENTRY PFNGLEWINIT)(void);
|
||||
typedef const GLubyte * GLEWAPIENTRY PFNGLEWGETERRORSTRING(GLenum error);
|
||||
PFNGLEWINIT* pGlewInit;
|
||||
PFNGLEWGETERRORSTRING* pGlewGetErrorString = nullptr;
|
||||
bool loadError = true;
|
||||
#define STR(s) #s
|
||||
#if defined(_M_IX86)
|
||||
/* Win32 GLEW uses __stdcall. */
|
||||
#define DNAMESTR(x,n) STR(_##x##@##n)
|
||||
#else
|
||||
/* x64 uses __cdecl. */
|
||||
#define DNAMESTR(x,n) STR(x)
|
||||
#endif
|
||||
pGlewInit = (PFNGLEWINIT*)SDL_LoadFunction(glewdll, DNAMESTR(glewInit,0));
|
||||
if (pGlewInit != NULL) {
|
||||
pGlewGetErrorString = (PFNGLEWGETERRORSTRING*)SDL_LoadFunction(
|
||||
glewdll, DNAMESTR(glewGetErrorString,4));
|
||||
if (pGlewGetErrorString != NULL) {
|
||||
loadError = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (loadError) {
|
||||
std::string sName(szName);
|
||||
|
||||
(void)SDL_ShowSimpleMessageBox(
|
||||
SDL_MESSAGEBOX_ERROR,
|
||||
szName,
|
||||
SDL_GetError(),
|
||||
NULL);
|
||||
return false;
|
||||
}
|
||||
int iResult = pGlewInit();
|
||||
if (iResult != GLEW_OK) {
|
||||
std::string sName(szName);
|
||||
|
||||
(void)SDL_ShowSimpleMessageBox(
|
||||
SDL_MESSAGEBOX_ERROR,
|
||||
szName,
|
||||
(const char*)pGlewGetErrorString(iResult),
|
||||
NULL);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int srgb;
|
||||
SDL_GL_GetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, &srgb);
|
||||
if (srgb && profile != SDL_GL_CONTEXT_PROFILE_ES)
|
||||
glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
|
||||
// In case the window is created with a different size than specified.
|
||||
int actualWidth, actualHeight;
|
||||
SDL_GetWindowSizeInPixels(pswMainWindow, &actualWidth, &actualHeight);
|
||||
resizeWindow(actualWidth, actualHeight);
|
||||
|
||||
initializeFPSTimer();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
GLAppSDL::finalize()
|
||||
{
|
||||
SDL_GL_DestroyContext(sgcGLContext);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
GLAppSDL::doEvent(SDL_Event* event)
|
||||
{
|
||||
switch (event->type) {
|
||||
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||
resizeWindow(event->window.data1, event->window.data2);
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
return AppBaseSDL::doEvent(event);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
GLAppSDL::drawFrame(uint32_t /*msTicks*/)
|
||||
{
|
||||
SDL_GL_SwapWindow(pswMainWindow);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
GLAppSDL::windowResized()
|
||||
{
|
||||
// Derived class can override as necessary.
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
GLAppSDL::resizeWindow(int width, int height)
|
||||
{
|
||||
w_width = width;
|
||||
w_height = height;
|
||||
windowResized();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
GLAppSDL::onFPSUpdate()
|
||||
{
|
||||
// Using onFPSUpdate avoids rewriting the title every frame.
|
||||
setWindowTitle();
|
||||
}
|
||||
|
||||
#if 0
|
||||
void
|
||||
GLAppSDL::setAppTitle(const char* const szExtra)
|
||||
{
|
||||
appTitle = name();
|
||||
if (szExtra != NULL && szExtra[0] != '\0') {
|
||||
appTitle += ": ";
|
||||
appTitle += szExtra;
|
||||
}
|
||||
setWindowTitle();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
GLAppSDL::setWindowTitle(const char* const szExtra)
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
ss << std::fixed << std::setprecision(2)
|
||||
<< lastFrameTime << "ms (" << fpsCounter.lastFPS << " fps)"
|
||||
<< " - " << szName;
|
||||
|
||||
if (szExtra != NULL && szExtra[0] != '\0') {
|
||||
ss << ": " << szExtra;
|
||||
}
|
||||
SDL_SetWindowTitle(pswMainWindow, ss.str().c_str());
|
||||
}
|
||||
|
||||
void
|
||||
GLAppSDL::setWindowTitle()
|
||||
{
|
||||
SDL_SetWindowTitle(pswMainWindow, appTitle.c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SDL_PLATFORM_WINDOWS
|
||||
// Windows specific code to use icon in module
|
||||
void
|
||||
setWindowsIcon(SDL_Window *sdlWindow) {
|
||||
HINSTANCE handle = ::GetModuleHandle(nullptr);
|
||||
// Identify icon by name rather than IDI_ macro to avoid having to
|
||||
// include application's resource.h.
|
||||
HICON icon = ::LoadIcon(handle, "MAIN_ICON");// MAKEINTRESOURCE(IDI_ICON1));
|
||||
if (icon != nullptr){
|
||||
HWND hwnd = (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(sdlWindow), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
|
||||
if (hwnd) {
|
||||
::SetClassLongPtr(hwnd, GCLP_HICON, reinterpret_cast<LONG_PTR>(icon));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// }
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
#ifndef GL_APP_SDL_H_1456211188
|
||||
#define GL_APP_SDL_H_1456211188
|
||||
|
||||
/* $Id: ac63511da134f2c25a9e1da86a36bc27b6198ae3 $ */
|
||||
|
||||
/*
|
||||
* Copyright 2015-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @file
|
||||
* @~English
|
||||
*
|
||||
* @brief Declaration of GLAppSDL base class for GL apps.
|
||||
*/
|
||||
|
||||
#include "AppBaseSDL.h"
|
||||
|
||||
|
||||
class GLAppSDL : public AppBaseSDL {
|
||||
public:
|
||||
GLAppSDL(const char* const name,
|
||||
int width, int height,
|
||||
const SDL_GLProfile profile,
|
||||
const int majorVersion,
|
||||
const int minorVersion)
|
||||
: AppBaseSDL(name),
|
||||
profile(profile),
|
||||
majorVersion(majorVersion), minorVersion(minorVersion)
|
||||
{
|
||||
appTitle = name;
|
||||
w_width = width;
|
||||
w_height = height;
|
||||
};
|
||||
virtual bool doEvent(SDL_Event* event);
|
||||
virtual void drawFrame(uint32_t msTicks);
|
||||
virtual void finalize();
|
||||
virtual bool initialize(Args& args);
|
||||
virtual void onFPSUpdate();
|
||||
virtual void resizeWindow(int width, int height);
|
||||
virtual void windowResized();
|
||||
|
||||
protected:
|
||||
SDL_GLContext sgcGLContext;
|
||||
|
||||
int w_width;
|
||||
int w_height;
|
||||
|
||||
const SDL_GLProfile profile;
|
||||
const int majorVersion;
|
||||
const int minorVersion;
|
||||
};
|
||||
|
||||
#endif /* GL_APP_SDL_H_1456211188 */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,172 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
#ifndef VULKAN_APP_SDL_H_1456211188
|
||||
#define VULKAN_APP_SDL_H_1456211188
|
||||
|
||||
/*
|
||||
* Copyright 2017-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <vector>
|
||||
#define VK_ENABLE_BETA_EXTENSIONS 1
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include <SDL3/SDL_vulkan.h> // Must be after vulkan.hpp.
|
||||
|
||||
#include "AppBaseSDL.h"
|
||||
#include "VulkanContext.h"
|
||||
#include "VulkanSwapchain.h"
|
||||
#include "vulkantextoverlay.hpp"
|
||||
#include "unused.h"
|
||||
|
||||
class VulkanAppSDL : public AppBaseSDL {
|
||||
public:
|
||||
VulkanAppSDL(const char* const name,
|
||||
int width, int height,
|
||||
const uint32_t version,
|
||||
bool enableTextOverlay)
|
||||
: AppBaseSDL(name), w_width(width), w_height(height),
|
||||
subOptimalPresentWarned(false), validate(false),
|
||||
vkVersion(version),
|
||||
enableTextOverlay(enableTextOverlay),
|
||||
textOverlay(nullptr)
|
||||
{
|
||||
// The overridden new below will zero the storage. Thus
|
||||
// we avoid a long list of initializers.
|
||||
appTitle = name;
|
||||
};
|
||||
virtual ~VulkanAppSDL();
|
||||
virtual bool doEvent(SDL_Event* event);
|
||||
virtual void drawFrame(uint32_t msTicks);
|
||||
virtual void finalize();
|
||||
virtual bool initialize(Args& args);
|
||||
virtual void onFPSUpdate();
|
||||
virtual void resizeWindow(int width, int height);
|
||||
virtual void windowResized();
|
||||
|
||||
static void* operator new(size_t size) {
|
||||
void* storage = ::operator new(size);
|
||||
memset(storage, 0, size);
|
||||
return storage;
|
||||
}
|
||||
|
||||
static void operator delete(void* storage, size_t) {
|
||||
::operator delete(storage);
|
||||
}
|
||||
|
||||
void updateTextOverlay();
|
||||
|
||||
// Called when the text overlay is updating
|
||||
// Can be overridden in derived class to add custom text to the overlay
|
||||
virtual void getOverlayText(float yOffset);
|
||||
|
||||
protected:
|
||||
bool createDevice();
|
||||
bool createInstance();
|
||||
bool createPipelineCache();
|
||||
bool createSemaphores();
|
||||
bool createSurface();
|
||||
bool createSwapchain();
|
||||
bool findGpu();
|
||||
void flushInitialCommands();
|
||||
bool initializeVulkan();
|
||||
bool prepareColorBuffers();
|
||||
bool prepareCommandBuffers();
|
||||
bool prepareDepthBuffer();
|
||||
bool prepareDescriptorLayout();
|
||||
void prepareFrame();
|
||||
bool preparePresentCommandBuffers();
|
||||
bool prepareRenderPass();
|
||||
bool preparePipeline();
|
||||
bool prepareDescriptorSet();
|
||||
bool prepareFramebuffers();
|
||||
void prepareTextOverlay();
|
||||
void submitFrame();
|
||||
|
||||
bool setupDebugReporting();
|
||||
|
||||
enum stencilRequirement { eNoStencil = 0, eStencil = 1 };
|
||||
enum depthRequirement { e16bits = 0, e24bits = 1, e32bits = 2 };
|
||||
bool getSupportedDepthFormat(vk::PhysicalDevice gpu,
|
||||
stencilRequirement requiredStencil,
|
||||
depthRequirement requiredDepth,
|
||||
vk::ImageTiling tiling,
|
||||
vk::Format& pFormat,
|
||||
vk::ImageAspectFlags& pAspectMask);
|
||||
|
||||
// Sets text on window title bar.
|
||||
void setWindowTitle();
|
||||
|
||||
void setImageLayout(VkImage image, VkImageAspectFlags aspectMask,
|
||||
VkImageLayout old_image_layout,
|
||||
VkImageLayout new_image_layout,
|
||||
VkAccessFlags srcAccessMask);
|
||||
|
||||
VKAPI_ATTR VkBool32 VKAPI_CALL
|
||||
debugFunc(VkFlags msgFlags, VkDebugReportObjectTypeEXT objType,
|
||||
uint64_t srcObject, size_t location, int32_t msgCode,
|
||||
const char *pLayerPrefix, const char *pMsg);
|
||||
std::string& wrapText(std::string& source, size_t width = 70,
|
||||
const std::string& whitespace = " \t\r");
|
||||
uint32_t showDebugReport(uint32_t mbFlags, const std::string title,
|
||||
std::string message, bool enableAbort);
|
||||
|
||||
static bool checkLayers(uint32_t nameCount, const char **names,
|
||||
uint32_t layerCount, VkLayerProperties *layers);
|
||||
static VKAPI_ATTR VkBool32 VKAPI_CALL
|
||||
debugFunc(VkFlags msgFlags, VkDebugReportObjectTypeEXT objType,
|
||||
uint64_t srcObject, size_t location, int32_t msgCode,
|
||||
const char *pLayerPrefix, const char *pMsg, void *pUserData);
|
||||
|
||||
bool prepared = false;
|
||||
// Set true if want presents v-sync'ed.
|
||||
bool enableVSync = false;
|
||||
|
||||
uint32_t w_width;
|
||||
uint32_t w_height;
|
||||
|
||||
bool subOptimalPresentWarned;
|
||||
bool validate;
|
||||
|
||||
std::vector<const char*> extensionNames;
|
||||
std::vector<const char*> deviceValidationLayers;
|
||||
|
||||
VkCommandBuffer setupCmdBuffer;
|
||||
VkSurfaceKHR vsSurface;
|
||||
|
||||
VulkanContext vkctx;
|
||||
|
||||
// Index of active framebuffer.
|
||||
uint32_t currentBuffer;
|
||||
|
||||
// Synchronization semaphores
|
||||
struct {
|
||||
// Swap chain image presentation
|
||||
VkSemaphore presentComplete;
|
||||
// Command buffer submission and execution
|
||||
VkSemaphore renderComplete;
|
||||
// Text overlay submission and execution
|
||||
VkSemaphore textOverlayComplete;
|
||||
} semaphores;
|
||||
|
||||
const uint32_t vkVersion;
|
||||
|
||||
// Saved for clean-up
|
||||
std::vector<vk::ShaderModule> shaderModules;
|
||||
|
||||
bool enableTextOverlay = false;
|
||||
VulkanTextOverlay *textOverlay;
|
||||
// List of shader modules created (stored for cleanup)
|
||||
|
||||
VkDebugReportCallbackEXT msgCallback;
|
||||
|
||||
PFN_vkCreateDebugReportCallbackEXT pfnCreateDebugReportCallbackEXT;
|
||||
PFN_vkDestroyDebugReportCallbackEXT pfnDestroyDebugReportCallbackEXT;
|
||||
PFN_vkDebugReportMessageEXT pfnDebugReportMessageEXT;
|
||||
};
|
||||
|
||||
#endif /* VULKAN_APP_SDL_H_1456211188 */
|
||||
@@ -0,0 +1,363 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2017-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @class VulkanContext
|
||||
* @~English
|
||||
*
|
||||
* @brief Class for holding and passing Vulkan context info to applications.
|
||||
*
|
||||
* @author Mark Callow, github.com/MarkCallow.
|
||||
*/
|
||||
|
||||
#include <exception>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <SDL3/SDL_iostream.h>
|
||||
#include "VulkanContext.h"
|
||||
// Until exceptions are used everywhere...
|
||||
#include "vulkancheckres.h"
|
||||
#include "ltexceptions.h"
|
||||
#include "unused.h"
|
||||
|
||||
vk::CommandBuffer
|
||||
VulkanContext::createCommandBuffer(vk::CommandBufferLevel level, bool begin)
|
||||
{
|
||||
vk::CommandBuffer cmdBuffer;
|
||||
|
||||
vk::CommandBufferAllocateInfo cmdBufAllocateInfo(
|
||||
commandPool,
|
||||
level,
|
||||
1);
|
||||
|
||||
vk::Result res
|
||||
= device.allocateCommandBuffers(&cmdBufAllocateInfo, &cmdBuffer);
|
||||
|
||||
if (res != vk::Result::eSuccess) {
|
||||
throw bad_vulkan_alloc((int)res, "device.allocateCommandBuffers");
|
||||
}
|
||||
|
||||
// If requested, also start the new command buffer
|
||||
if (begin)
|
||||
{
|
||||
vk::CommandBufferBeginInfo cmdBufInfo;
|
||||
res = cmdBuffer.begin(&cmdBufInfo);
|
||||
if (res != vk::Result::eSuccess) {
|
||||
throw bad_vulkan_alloc((int)res, "cmdBuffer.begin");
|
||||
}
|
||||
}
|
||||
|
||||
return cmdBuffer;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
VulkanContext::flushCommandBuffer(vk::CommandBuffer& cmdBuffer,
|
||||
bool free)
|
||||
{
|
||||
if (!cmdBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
cmdBuffer.end();
|
||||
|
||||
vk::SubmitInfo submitInfo;
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &cmdBuffer;
|
||||
|
||||
vk::Result res = queue.submit(1, &submitInfo, nullptr);
|
||||
if (res != vk::Result::eSuccess) {
|
||||
if (res == vk::Result::eErrorDeviceLost) {
|
||||
throw std::runtime_error("Vulkan device lost.");
|
||||
} else {
|
||||
throw bad_vulkan_alloc((int)res, "queue.submit");
|
||||
}
|
||||
}
|
||||
|
||||
queue.waitIdle();
|
||||
|
||||
if (free) {
|
||||
device.freeCommandBuffers(commandPool, 1, &cmdBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
VulkanContext::createDrawCommandBuffers()
|
||||
{
|
||||
VkCommandBufferAllocateInfo aInfo;
|
||||
aInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
aInfo.pNext = NULL;
|
||||
aInfo.commandPool = commandPool;
|
||||
aInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
aInfo.commandBufferCount = 1;
|
||||
|
||||
drawCmdBuffers.resize(swapchain.imageCount);
|
||||
for (uint32_t i = 0; i < swapchain.imageCount; i++) {
|
||||
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &aInfo,
|
||||
&drawCmdBuffers[i]));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
VulkanContext::createPresentCommandBuffers()
|
||||
{
|
||||
VkCommandBufferAllocateInfo aInfo;
|
||||
aInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
aInfo.pNext = NULL;
|
||||
aInfo.commandPool = commandPool;
|
||||
aInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
aInfo.commandBufferCount = 1;
|
||||
|
||||
prePresentCmdBuffers.resize(swapchain.imageCount);
|
||||
postPresentCmdBuffers.resize(swapchain.imageCount);
|
||||
for (uint32_t i = 0; i < swapchain.imageCount; i++) {
|
||||
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &aInfo,
|
||||
&prePresentCmdBuffers[i]));
|
||||
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &aInfo,
|
||||
&postPresentCmdBuffers[i]));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
VulkanContext::destroyDrawCommandBuffers()
|
||||
{
|
||||
if (drawCmdBuffers.size() > 0) {
|
||||
vkFreeCommandBuffers(device, commandPool,
|
||||
static_cast<uint32_t>(drawCmdBuffers.size()),
|
||||
drawCmdBuffers.data());
|
||||
}
|
||||
for (uint32_t i = 0; i < drawCmdBuffers.size(); ++i) {
|
||||
drawCmdBuffers[i] = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
VulkanContext::destroyPresentCommandBuffers()
|
||||
{
|
||||
vkFreeCommandBuffers(device, commandPool,
|
||||
static_cast<uint32_t>(drawCmdBuffers.size()),
|
||||
prePresentCmdBuffers.data());
|
||||
vkFreeCommandBuffers(device, commandPool,
|
||||
static_cast<uint32_t>(drawCmdBuffers.size()),
|
||||
postPresentCmdBuffers.data());
|
||||
}
|
||||
|
||||
bool
|
||||
VulkanContext::checkDrawCommandBuffers()
|
||||
{
|
||||
for (auto& cmdBuffer : drawCmdBuffers)
|
||||
{
|
||||
if (cmdBuffer == VK_NULL_HANDLE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
VulkanContext::createBuffer(vk::BufferUsageFlags usageFlags,
|
||||
vk::MemoryPropertyFlags memoryPropertyFlags,
|
||||
vk::DeviceSize size,
|
||||
void * data,
|
||||
vk::Buffer* buffer,
|
||||
vk::DeviceMemory* memory)
|
||||
{
|
||||
vk::MemoryRequirements memReqs;
|
||||
vk::MemoryAllocateInfo memAlloc(0, 0);
|
||||
vk::BufferCreateInfo bufferCreateInfo({}, size, usageFlags);
|
||||
|
||||
vk::Result res = device.createBuffer(&bufferCreateInfo, nullptr, buffer);
|
||||
if (res != vk::Result::eSuccess) {
|
||||
return false;
|
||||
}
|
||||
|
||||
device.getBufferMemoryRequirements(*buffer, &memReqs);
|
||||
memAlloc.allocationSize = memReqs.size;
|
||||
memAlloc.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits,
|
||||
memoryPropertyFlags);
|
||||
res = device.allocateMemory(&memAlloc, nullptr, memory);
|
||||
if (res == vk::Result::eSuccess) {
|
||||
if (data != nullptr)
|
||||
{
|
||||
void *mapped;
|
||||
mapped = device.mapMemory(*memory, 0, size, {});
|
||||
memcpy(mapped, data, (size_t)size);
|
||||
device.unmapMemory(*memory);
|
||||
}
|
||||
device.bindBufferMemory(*buffer, *memory, 0);
|
||||
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
VulkanContext::createBuffer(vk::BufferUsageFlags usage,
|
||||
vk::DeviceSize size,
|
||||
void* data,
|
||||
vk::Buffer* buffer,
|
||||
vk::DeviceMemory* memory)
|
||||
{
|
||||
return createBuffer(usage, vk::MemoryPropertyFlagBits::eHostVisible,
|
||||
size, data, buffer, memory);
|
||||
}
|
||||
|
||||
bool
|
||||
VulkanContext::createBuffer(vk::BufferUsageFlags usage,
|
||||
vk::DeviceSize size,
|
||||
void* data,
|
||||
vk::Buffer* buffer,
|
||||
vk::DeviceMemory* memory,
|
||||
vk::DescriptorBufferInfo* descriptor)
|
||||
{
|
||||
bool res = createBuffer(usage, size, data, buffer, memory);
|
||||
if (res)
|
||||
{
|
||||
descriptor->offset = 0;
|
||||
descriptor->buffer = *buffer;
|
||||
descriptor->range = size;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
VulkanContext::createBuffer(vk::BufferUsageFlags usage,
|
||||
vk::MemoryPropertyFlags memoryPropertyFlags,
|
||||
vk::DeviceSize size,
|
||||
void* data,
|
||||
vk::Buffer* buffer,
|
||||
vk::DeviceMemory* memory,
|
||||
vk::DescriptorBufferInfo* descriptor)
|
||||
{
|
||||
bool res = createBuffer(usage, memoryPropertyFlags, size, data, buffer, memory);
|
||||
if (res)
|
||||
{
|
||||
descriptor->offset = 0;
|
||||
descriptor->buffer = *buffer;
|
||||
descriptor->range = size;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
VulkanContext::getMemoryType(uint32_t typeBits,
|
||||
vk::MemoryPropertyFlags requirementsMask,
|
||||
uint32_t *typeIndex) const
|
||||
{
|
||||
// Search memtypes to find first index with desired properties
|
||||
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
|
||||
if ((typeBits & 1) == 1) {
|
||||
// Type is available, does it match user properties?
|
||||
if ((memoryProperties.memoryTypes[i].propertyFlags &
|
||||
requirementsMask) == requirementsMask) {
|
||||
*typeIndex = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
typeBits >>= 1;
|
||||
}
|
||||
// No memory types matched, return failure
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
VulkanContext::getMemoryType(uint32_t typeBits,
|
||||
vk::MemoryPropertyFlags requirementsMask) const
|
||||
{
|
||||
// Search memtypes to find first index with desired properties
|
||||
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
|
||||
if ((typeBits & 1) == 1) {
|
||||
// Type is available, does it match user properties?
|
||||
if ((memoryProperties.memoryTypes[i].propertyFlags &
|
||||
requirementsMask) == requirementsMask) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
typeBits >>= 1;
|
||||
}
|
||||
// No memory types matched, return failure
|
||||
// TODO throw error
|
||||
return 0;
|
||||
}
|
||||
|
||||
vk::PipelineShaderStageCreateInfo
|
||||
VulkanContext::loadShader(std::string filename,
|
||||
vk::ShaderStageFlagBits stage,
|
||||
const char* const modname)
|
||||
{
|
||||
vk::PipelineShaderStageCreateInfo shaderStage({}, stage);
|
||||
shaderStage.module = loadShader(filename);
|
||||
shaderStage.pName = modname;
|
||||
return shaderStage;
|
||||
}
|
||||
|
||||
vk::ShaderModule
|
||||
VulkanContext::loadShader(std::string filename)
|
||||
{
|
||||
size_t codeSize;
|
||||
uint32_t* shaderCode;
|
||||
|
||||
shaderCode = readSpv(filename.c_str(), &codeSize);
|
||||
|
||||
vk::ShaderModule shaderModule;
|
||||
vk::ShaderModuleCreateInfo moduleCreateInfo({}, codeSize, shaderCode);
|
||||
|
||||
vk::Result res
|
||||
= device.createShaderModule(&moduleCreateInfo, NULL, &shaderModule);
|
||||
if (res != vk::Result::eSuccess) {
|
||||
throw bad_vulkan_alloc((int)res, "device.createShaderModule");
|
||||
}
|
||||
|
||||
delete[] shaderCode;
|
||||
|
||||
assert(shaderModule);
|
||||
return shaderModule;
|
||||
}
|
||||
|
||||
uint32_t*
|
||||
VulkanContext::readSpv(const char *filename, size_t *pSize) {
|
||||
size_t size;
|
||||
U_ASSERT_ONLY size_t retval;
|
||||
uint32_t* shader_code;
|
||||
|
||||
SDL_IOStream* io = SDL_IOFromFile(filename, "rb");
|
||||
if (!io) {
|
||||
std::stringstream message;
|
||||
|
||||
// String returned by SDL_GetError() includes file name.
|
||||
message << "Open of shader failed: " << SDL_GetError();
|
||||
throw std::runtime_error(message.str());
|
||||
}
|
||||
|
||||
size = (size_t)SDL_GetIOSize(io);
|
||||
|
||||
// Round-up to next 4-byte size.
|
||||
shader_code = new uint32_t[(size + 3)/4];
|
||||
retval = SDL_ReadIO(io, shader_code, size);
|
||||
assert(retval == size);
|
||||
|
||||
*pSize = size;
|
||||
|
||||
SDL_CloseIO(io);
|
||||
return shader_code;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
#ifndef VULKAN_TEXTURE_H_229895365400979164311947449304284143508
|
||||
#define VULKAN_TEXTURE_H_229895365400979164311947449304284143508
|
||||
|
||||
/*
|
||||
* Copyright 2017-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
#define VK_ENABLE_BETA_EXTENSIONS 1
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include "VulkanSwapchain.h"
|
||||
|
||||
struct VulkanDepthBuffer {
|
||||
VkFormat format;
|
||||
VkImage image;
|
||||
VkMemoryAllocateInfo memAlloc;
|
||||
VkDeviceMemory mem;
|
||||
VkImageView view;
|
||||
};
|
||||
|
||||
struct VulkanContext {
|
||||
vk::Instance instance;
|
||||
vk::PhysicalDevice gpu;
|
||||
vk::PhysicalDeviceFeatures gpuFeatures;
|
||||
#if VK_KHR_portability_subset
|
||||
vk::PhysicalDevicePortabilitySubsetFeaturesKHR gpuPortabilityFeatures;
|
||||
#endif
|
||||
vk::PhysicalDeviceProperties gpuProperties;
|
||||
vk::PhysicalDeviceMemoryProperties memoryProperties;
|
||||
vk::Device device;
|
||||
vk::CommandPool commandPool;
|
||||
vk::Queue queue;
|
||||
|
||||
bool gpuIsPortabilitySubsetDevice = false;
|
||||
|
||||
struct {
|
||||
bool pvrtc = false;
|
||||
bool astc_hdr = false;
|
||||
bool astc_3d = false;
|
||||
} enabledDeviceExtensions;
|
||||
std::vector<VkCommandBuffer> drawCmdBuffers;
|
||||
std::vector<VkCommandBuffer> postPresentCmdBuffers;
|
||||
std::vector<VkCommandBuffer> prePresentCmdBuffers;
|
||||
VkRenderPass renderPass;
|
||||
// Pipeline stage flags for the submit info structure
|
||||
const VkPipelineStageFlags submitPipelineStages =
|
||||
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||
// Contains command buffers and semaphores to be presented to the queue
|
||||
VkSubmitInfo drawCmdSubmitInfo;
|
||||
|
||||
vk::DescriptorPool descriptorPool;
|
||||
VkPipelineCache pipelineCache;
|
||||
|
||||
VulkanSwapchain swapchain;
|
||||
// List of frame buffers (same as number of swap chain images)
|
||||
std::vector<VkFramebuffer>framebuffers;
|
||||
VulkanDepthBuffer depthBuffer;
|
||||
|
||||
// Create a new command buffer, opening it for command entry,
|
||||
// if requested.
|
||||
vk::CommandBuffer createCommandBuffer(vk::CommandBufferLevel level,
|
||||
bool begin);
|
||||
// End a command buffer, submit it to the queue and free, if requested.
|
||||
// Note : Waits for the queue to become idle
|
||||
void flushCommandBuffer(vk::CommandBuffer& commandBuffer, bool free);
|
||||
|
||||
// Create a command buffer for each image in the swap chain.
|
||||
bool createDrawCommandBuffers();
|
||||
bool createPresentCommandBuffers();
|
||||
bool checkDrawCommandBuffers();
|
||||
void destroyDrawCommandBuffers();
|
||||
void destroyPresentCommandBuffers();
|
||||
|
||||
|
||||
bool getMemoryType(uint32_t typeBits,
|
||||
vk::MemoryPropertyFlags requirementsMask,
|
||||
uint32_t *typeIndex) const;
|
||||
uint32_t getMemoryType(uint32_t typeBits,
|
||||
vk::MemoryPropertyFlags requirementsMask) const;
|
||||
|
||||
// Create a buffer, fill it with data (if != NULL) and bind buffer memory
|
||||
bool createBuffer(
|
||||
vk::BufferUsageFlags usageFlags,
|
||||
vk::MemoryPropertyFlags memoryPropertyFlags,
|
||||
vk::DeviceSize size,
|
||||
void *data,
|
||||
vk::Buffer *buffer,
|
||||
vk::DeviceMemory *memory);
|
||||
// This version always uses HOST_VISIBLE memory
|
||||
bool createBuffer(
|
||||
vk::BufferUsageFlags usage,
|
||||
vk::DeviceSize size,
|
||||
void *data,
|
||||
vk::Buffer *buffer,
|
||||
vk::DeviceMemory *memory);
|
||||
// Overload that assigns buffer info to descriptor
|
||||
bool createBuffer(
|
||||
vk::BufferUsageFlags usage,
|
||||
vk::DeviceSize size,
|
||||
void* data,
|
||||
vk::Buffer* buffer,
|
||||
vk::DeviceMemory* memory,
|
||||
vk::DescriptorBufferInfo* descriptor);
|
||||
// Overload to pass memory property flags
|
||||
bool createBuffer(
|
||||
vk::BufferUsageFlags usage,
|
||||
vk::MemoryPropertyFlags memoryPropertyFlags,
|
||||
vk::DeviceSize size,
|
||||
void* data,
|
||||
vk::Buffer* buffer,
|
||||
vk::DeviceMemory* memory,
|
||||
vk::DescriptorBufferInfo* descriptor);
|
||||
|
||||
vk::PipelineShaderStageCreateInfo loadShader(std::string filename,
|
||||
vk::ShaderStageFlagBits stage,
|
||||
const char* const modname = "main");
|
||||
vk::ShaderModule loadShader(std::string filename);
|
||||
uint32_t* readSpv(const char *filename, size_t *pSize);
|
||||
|
||||
bool gpuSupportsSwizzle() {
|
||||
#if VK_KHR_portability_subset
|
||||
return !gpuIsPortabilitySubsetDevice
|
||||
|| gpuPortabilityFeatures.imageViewFormatSwizzle;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* VULKAN_TEXTURE_H_229895365400979164311947449304284143508 */
|
||||
@@ -0,0 +1,434 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2017-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @class VulkanSwapchain
|
||||
* @~English
|
||||
*
|
||||
* @brief Manage the swapchain for a Vulkan app.
|
||||
*
|
||||
* A swap chain is a collection of image buffers used for rendering
|
||||
* The images can then be presented to the windowing system for display.
|
||||
*
|
||||
* @author Mark Callow, github.com/MarkCallow.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#else
|
||||
#endif
|
||||
|
||||
#include "VulkanSwapchain.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
#include "AppBaseSDL.h"
|
||||
#include "unused.h"
|
||||
|
||||
#define ERROR_RETURN(msg) \
|
||||
(void)SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, theApp->name(), \
|
||||
msg, NULL); \
|
||||
return false;
|
||||
|
||||
#define GET_INSTANCE_PROC_ADDR(inst, entrypoint) \
|
||||
{ \
|
||||
pfn##entrypoint = \
|
||||
(PFN_vk##entrypoint)vkGetInstanceProcAddr(inst, "vk"#entrypoint); \
|
||||
if (pfn##entrypoint == NULL) { \
|
||||
ERROR_RETURN("vkGetInstanceProcAddr: unable to find vk"#entrypoint); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define GET_DEVICE_PROC_ADDR(device, entrypoint) \
|
||||
{ \
|
||||
pfn##entrypoint = \
|
||||
(PFN_vk##entrypoint)vkGetDeviceProcAddr(device, "vk"#entrypoint); \
|
||||
if (pfn##entrypoint == NULL) { \
|
||||
ERROR_RETURN("vkGetDeviceProcAddr: unable to find vk"#entrypoint); \
|
||||
} \
|
||||
}
|
||||
|
||||
// Creates an os specific surface
|
||||
// Tries to find a graphics and a present queue
|
||||
bool
|
||||
VulkanSwapchain::initSurface(SDL_Window* window)
|
||||
{
|
||||
U_ASSERT_ONLY VkResult err;
|
||||
|
||||
if (!SDL_Vulkan_CreateSurface(window, instance, nullptr, &surface)) {
|
||||
std::string msg = "SDL_CreateVulkanSurface failed: ";
|
||||
msg += SDL_GetError();
|
||||
ERROR_RETURN(msg.c_str());
|
||||
}
|
||||
|
||||
// Get available queue family properties
|
||||
uint32_t queueCount;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, NULL);
|
||||
assert(queueCount >= 1);
|
||||
|
||||
std::vector<VkQueueFamilyProperties> queueProps(queueCount);
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount,
|
||||
queueProps.data());
|
||||
|
||||
// Iterate over the queues looking for ones which support presenting.
|
||||
std::vector<VkBool32> supportsPresent(queueCount);
|
||||
for (uint32_t i = 0; i < queueCount; i++) {
|
||||
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface,
|
||||
&supportsPresent[i]);
|
||||
}
|
||||
|
||||
// Search for a graphics- and present-capable queue.
|
||||
uint32_t graphicsQueueIndex = UINT32_MAX;
|
||||
uint32_t presentQueueIndex = UINT32_MAX;
|
||||
for (uint32_t i = 0; i < queueCount; i++) {
|
||||
if ((queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0)
|
||||
{
|
||||
if (graphicsQueueIndex == UINT32_MAX)
|
||||
{
|
||||
graphicsQueueIndex = i;
|
||||
}
|
||||
|
||||
if (supportsPresent[i] == VK_TRUE)
|
||||
{
|
||||
graphicsQueueIndex = i;
|
||||
presentQueueIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (presentQueueIndex == UINT32_MAX) {
|
||||
// If there's no queue that supports both present and graphics
|
||||
// try to find a separate present queue
|
||||
for (uint32_t i = 0; i < queueCount; ++i)
|
||||
{
|
||||
if (supportsPresent[i] == VK_TRUE)
|
||||
{
|
||||
presentQueueIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit if either a graphics or a presenting queue hasn't been found
|
||||
if (graphicsQueueIndex == UINT32_MAX
|
||||
|| presentQueueIndex == UINT32_MAX)
|
||||
{
|
||||
ERROR_RETURN("Could not find a graphics or presenting queue!");
|
||||
}
|
||||
|
||||
// TODO: Add support for separate graphics and presenting queue
|
||||
if (graphicsQueueIndex != presentQueueIndex)
|
||||
{
|
||||
ERROR_RETURN("Separate graphics and present queues not yet supported!");
|
||||
}
|
||||
|
||||
queueIndex = graphicsQueueIndex;
|
||||
|
||||
// Get list of supported surface formats
|
||||
uint32_t formatCount;
|
||||
err = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface,
|
||||
&formatCount, NULL);
|
||||
assert(err == VK_SUCCESS);
|
||||
assert(formatCount > 0);
|
||||
|
||||
std::vector<VkSurfaceFormatKHR> surfaceFormats(formatCount);
|
||||
err = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface,
|
||||
&formatCount,
|
||||
surfaceFormats.data());
|
||||
assert(err == VK_SUCCESS);
|
||||
|
||||
// If the surface format list only includes one entry with
|
||||
// VK_FORMAT_UNDEFINED, there is no preferred format.
|
||||
// Assume VK_FORMAT_B8G8R8A8_RGB.
|
||||
// TODO: Consider passing in desired format from app.
|
||||
if ((formatCount == 1) && (surfaceFormats[0].format == VK_FORMAT_UNDEFINED))
|
||||
{
|
||||
colorFormat = VK_FORMAT_B8G8R8A8_SRGB;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(formatCount >= 1);
|
||||
uint32_t i;
|
||||
for (i = 0; i < formatCount; i++) {
|
||||
if (surfaceFormats[i].format == VK_FORMAT_B8G8R8A8_SRGB) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == formatCount) {
|
||||
// Pick the first available, if no SRGB.
|
||||
// FIXME probably should raise an error...
|
||||
i = 0;
|
||||
}
|
||||
colorFormat = surfaceFormats[i].format;
|
||||
}
|
||||
colorSpace = surfaceFormats[0].colorSpace;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Connect to the instance and device and get all required function pointers
|
||||
bool
|
||||
VulkanSwapchain::connectDevice(VkDevice targetDevice)
|
||||
{
|
||||
this->device = targetDevice;
|
||||
#if USE_FUNCPTRS_FOR_KHR_EXTS
|
||||
GET_DEVICE_PROC_ADDR(device, CreateSwapchainKHR);
|
||||
GET_DEVICE_PROC_ADDR(device, DestroySwapchainKHR);
|
||||
GET_DEVICE_PROC_ADDR(device, GetSwapchainImagesKHR);
|
||||
GET_DEVICE_PROC_ADDR(device, AcquireNextImageKHR);
|
||||
GET_DEVICE_PROC_ADDR(device, QueuePresentKHR);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Connect to the instance and device and get all required function pointers
|
||||
bool
|
||||
VulkanSwapchain::connectInstance(VkInstance targetInstance,
|
||||
VkPhysicalDevice targetPhysicalDevice)
|
||||
{
|
||||
this->instance = targetInstance;
|
||||
this->physicalDevice = targetPhysicalDevice;
|
||||
#if USE_FUNCPTRS_FOR_KHR_EXTS
|
||||
GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfaceSupportKHR);
|
||||
GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfaceCapabilitiesKHR);
|
||||
GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfaceFormatsKHR);
|
||||
GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfacePresentModesKHR);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Create the swap chain and get images with given width and height
|
||||
void
|
||||
VulkanSwapchain::create(uint32_t *width, uint32_t *height,
|
||||
bool vsync)
|
||||
{
|
||||
U_ASSERT_ONLY VkResult err;
|
||||
VkSwapchainKHR oldSwapchain = swapchain;
|
||||
|
||||
// Get physical device surface properties and formats
|
||||
VkSurfaceCapabilitiesKHR surfCaps;
|
||||
err = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface,
|
||||
&surfCaps);
|
||||
assert(err == VK_SUCCESS);
|
||||
|
||||
// Get available present modes
|
||||
uint32_t presentModeCount;
|
||||
err = vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface,
|
||||
&presentModeCount, NULL);
|
||||
assert(err == VK_SUCCESS);
|
||||
assert(presentModeCount > 0);
|
||||
|
||||
std::vector<VkPresentModeKHR> presentModes(presentModeCount);
|
||||
|
||||
err = vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface,
|
||||
&presentModeCount,
|
||||
presentModes.data());
|
||||
assert(err == VK_SUCCESS);
|
||||
|
||||
VkExtent2D swapchainExtent = {};
|
||||
// width and height are either both 0xFFFFFFFF, or both not 0xFFFFFFFF.
|
||||
if (surfCaps.currentExtent.width == UINT32_MAX)
|
||||
{
|
||||
// If the surface size is undefined, the size is set to
|
||||
// the size of the images requested.
|
||||
swapchainExtent.width = *width;
|
||||
swapchainExtent.height = *height;
|
||||
}
|
||||
else
|
||||
{
|
||||
swapchainExtent = surfCaps.currentExtent;
|
||||
*width = surfCaps.currentExtent.width;
|
||||
*height = surfCaps.currentExtent.height;
|
||||
}
|
||||
|
||||
|
||||
// Select a present mode for the swapchain
|
||||
|
||||
// The VK_PRESENT_MODE_FIFO_KHR mode must always be present as per spec
|
||||
// This mode waits for the vertical blank ("v-sync")
|
||||
VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR;
|
||||
|
||||
// If v-sync is not requested, try to find a mailbox mode if present
|
||||
// It's the lowest latency non-tearing present mode available
|
||||
if (!vsync)
|
||||
{
|
||||
for (size_t i = 0; i < presentModeCount; i++)
|
||||
{
|
||||
if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR)
|
||||
{
|
||||
swapchainPresentMode = VK_PRESENT_MODE_MAILBOX_KHR;
|
||||
break;
|
||||
}
|
||||
if ((swapchainPresentMode != VK_PRESENT_MODE_MAILBOX_KHR)
|
||||
&& (presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR))
|
||||
{
|
||||
swapchainPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the number of images
|
||||
uint32_t desiredNumberOfSwapchainImages = surfCaps.minImageCount + 1;
|
||||
if ((surfCaps.maxImageCount > 0)
|
||||
&& (desiredNumberOfSwapchainImages > surfCaps.maxImageCount))
|
||||
{
|
||||
desiredNumberOfSwapchainImages = surfCaps.maxImageCount;
|
||||
}
|
||||
|
||||
VkSurfaceTransformFlagsKHR preTransform;
|
||||
if (surfCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
|
||||
preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||
} else {
|
||||
preTransform = surfCaps.currentTransform;
|
||||
}
|
||||
|
||||
VkSwapchainCreateInfoKHR swapchainCI = {};
|
||||
swapchainCI.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
|
||||
swapchainCI.pNext = NULL;
|
||||
swapchainCI.surface = surface;
|
||||
swapchainCI.minImageCount = desiredNumberOfSwapchainImages;
|
||||
swapchainCI.imageFormat = colorFormat;
|
||||
swapchainCI.imageColorSpace = colorSpace;
|
||||
swapchainCI.imageExtent = { swapchainExtent.width, swapchainExtent.height };
|
||||
swapchainCI.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
swapchainCI.preTransform = (VkSurfaceTransformFlagBitsKHR)preTransform;
|
||||
swapchainCI.imageArrayLayers = 1;
|
||||
swapchainCI.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
swapchainCI.queueFamilyIndexCount = 0;
|
||||
swapchainCI.pQueueFamilyIndices = NULL;
|
||||
swapchainCI.presentMode = swapchainPresentMode;
|
||||
swapchainCI.oldSwapchain = oldSwapchain;
|
||||
swapchainCI.clipped = true;
|
||||
swapchainCI.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||
|
||||
err = vkCreateSwapchainKHR(device, &swapchainCI, nullptr, &swapchain);
|
||||
assert(err == VK_SUCCESS);
|
||||
|
||||
// If an existing swap chain is re-created, destroy the old swap chain
|
||||
// This also cleans up all the presentable images
|
||||
if (oldSwapchain != VK_NULL_HANDLE)
|
||||
{
|
||||
for (uint32_t i = 0; i < imageCount; i++)
|
||||
{
|
||||
vkDestroyImageView(device, buffers[i].view, nullptr);
|
||||
}
|
||||
vkDestroySwapchainKHR(device, oldSwapchain, nullptr);
|
||||
}
|
||||
|
||||
err = vkGetSwapchainImagesKHR(device, swapchain, &imageCount, NULL);
|
||||
assert(err == VK_SUCCESS);
|
||||
|
||||
// Get the swap chain images
|
||||
images.resize(imageCount);
|
||||
err = vkGetSwapchainImagesKHR(device, swapchain,
|
||||
&imageCount, images.data());
|
||||
assert(err == VK_SUCCESS);
|
||||
|
||||
// Get the swap chain buffers containing the image and imageview
|
||||
buffers.resize(imageCount);
|
||||
for (uint32_t i = 0; i < imageCount; i++)
|
||||
{
|
||||
VkImageViewCreateInfo colorAttachmentView = {};
|
||||
colorAttachmentView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
colorAttachmentView.pNext = NULL;
|
||||
colorAttachmentView.format = colorFormat;
|
||||
colorAttachmentView.components = {
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY
|
||||
};
|
||||
colorAttachmentView.subresourceRange.aspectMask
|
||||
= VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
colorAttachmentView.subresourceRange.baseMipLevel = 0;
|
||||
colorAttachmentView.subresourceRange.levelCount = 1;
|
||||
colorAttachmentView.subresourceRange.baseArrayLayer = 0;
|
||||
colorAttachmentView.subresourceRange.layerCount = 1;
|
||||
colorAttachmentView.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
colorAttachmentView.flags = 0;
|
||||
|
||||
buffers[i].image = images[i];
|
||||
|
||||
colorAttachmentView.image = buffers[i].image;
|
||||
|
||||
err = vkCreateImageView(device, &colorAttachmentView, nullptr,
|
||||
&buffers[i].view);
|
||||
assert(err == VK_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Acquires the next image in the swap chain
|
||||
VkResult
|
||||
VulkanSwapchain::acquireNextImage(VkSemaphore presentCompleteSemaphore,
|
||||
uint32_t *currentBuffer)
|
||||
{
|
||||
return vkAcquireNextImageKHR(device, swapchain, UINT64_MAX,
|
||||
presentCompleteSemaphore,
|
||||
(VkFence)nullptr,
|
||||
currentBuffer);
|
||||
}
|
||||
|
||||
|
||||
// Present the current image to the queue
|
||||
VkResult
|
||||
VulkanSwapchain::queuePresent(VkQueue queue, uint32_t currentBuffer)
|
||||
{
|
||||
VkPresentInfoKHR presentInfo = {};
|
||||
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||
presentInfo.pNext = NULL;
|
||||
presentInfo.swapchainCount = 1;
|
||||
presentInfo.pSwapchains = &swapchain;
|
||||
presentInfo.pImageIndices = ¤tBuffer;
|
||||
return vkQueuePresentKHR(queue, &presentInfo);
|
||||
}
|
||||
|
||||
|
||||
// Present the current image to the queue when semaphore signaled.
|
||||
VkResult
|
||||
VulkanSwapchain::queuePresent(VkQueue queue, uint32_t currentBuffer,
|
||||
VkSemaphore waitSemaphore)
|
||||
{
|
||||
VkPresentInfoKHR presentInfo = {};
|
||||
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||
presentInfo.pNext = NULL;
|
||||
presentInfo.swapchainCount = 1;
|
||||
presentInfo.pSwapchains = &swapchain;
|
||||
presentInfo.pImageIndices = ¤tBuffer;
|
||||
if (waitSemaphore != VK_NULL_HANDLE)
|
||||
{
|
||||
presentInfo.pWaitSemaphores = &waitSemaphore;
|
||||
presentInfo.waitSemaphoreCount = 1;
|
||||
}
|
||||
return vkQueuePresentKHR(queue, &presentInfo);
|
||||
}
|
||||
|
||||
|
||||
// Free all Vulkan resources used by the swap chain
|
||||
void
|
||||
VulkanSwapchain::cleanup()
|
||||
{
|
||||
for (uint32_t i = 0; i < imageCount; i++)
|
||||
{
|
||||
vkDestroyImageView(device, buffers[i].view, nullptr);
|
||||
}
|
||||
vkDestroySwapchainKHR(device, swapchain, nullptr);
|
||||
vkDestroySurfaceKHR(instance, surface, nullptr);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2017-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#if !defined(USE_FUNCPTRS_FOR_KHR_EXTS)
|
||||
#define USE_FUNCPTRS_FOR_KHR_EXTS 0
|
||||
#endif
|
||||
|
||||
typedef struct _SwapchainBuffers {
|
||||
VkImage image;
|
||||
VkImageView view;
|
||||
} SwapchainBuffer;
|
||||
|
||||
class VulkanSwapchain
|
||||
{
|
||||
public:
|
||||
VkFormat colorFormat;
|
||||
VkColorSpaceKHR colorSpace;
|
||||
|
||||
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
|
||||
|
||||
uint32_t imageCount;
|
||||
std::vector<VkImage> images;
|
||||
std::vector<SwapchainBuffer> buffers;
|
||||
|
||||
// Index of the detected graphics- and present-capable device queue.
|
||||
uint32_t queueIndex = UINT32_MAX;
|
||||
|
||||
// Creates an OS specific surface.
|
||||
// Looks for a graphics and a present queue
|
||||
bool initSurface(struct SDL_Window* window);
|
||||
|
||||
// Connect to device and get required device function pointers.
|
||||
bool connectDevice(VkDevice device);
|
||||
|
||||
// Connect to instance and get required instance function pointers.
|
||||
bool connectInstance(VkInstance instance,
|
||||
VkPhysicalDevice physicalDevice);
|
||||
|
||||
// Create the swap chain and get images with given width and height
|
||||
void create(uint32_t *width, uint32_t *height,
|
||||
bool vsync = false);
|
||||
|
||||
// Acquires the next image in the swap chain
|
||||
VkResult acquireNextImage(VkSemaphore presentCompleteSemaphore,
|
||||
uint32_t *currentBuffer);
|
||||
|
||||
// Present the current image to the queue
|
||||
VkResult queuePresent(VkQueue queue, uint32_t currentBuffer);
|
||||
|
||||
// Present the current image to the queue
|
||||
VkResult queuePresent(VkQueue queue, uint32_t currentBuffer,
|
||||
VkSemaphore waitSemaphore);
|
||||
|
||||
|
||||
// Free all Vulkan resources used by the swap chain
|
||||
void cleanup();
|
||||
|
||||
private:
|
||||
VkInstance instance;
|
||||
VkDevice device;
|
||||
VkPhysicalDevice physicalDevice;
|
||||
VkSurfaceKHR surface;
|
||||
#if USE_FUNCPTRS_FOR_KHR_EXTS
|
||||
PFN_vkGetPhysicalDeviceSurfaceSupportKHR
|
||||
pfnGetPhysicalDeviceSurfaceSupportKHR;
|
||||
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR
|
||||
pfnGetPhysicalDeviceSurfaceCapabilitiesKHR;
|
||||
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR
|
||||
pfnGetPhysicalDeviceSurfaceFormatsKHR;
|
||||
PFN_vkGetPhysicalDeviceSurfacePresentModesKHR
|
||||
pfnGetPhysicalDeviceSurfacePresentModesKHR;
|
||||
|
||||
PFN_vkCreateSwapchainKHR pfnCreateSwapchainKHR;
|
||||
PFN_vkDestroySwapchainKHR pfnDestroySwapchainKHR;
|
||||
PFN_vkGetSwapchainImagesKHR pfnGetSwapchainImagesKHR;
|
||||
PFN_vkAcquireNextImageKHR pfnAcquireNextImageKHR;
|
||||
PFN_vkQueuePresentKHR pfnQueuePresentKHR;
|
||||
|
||||
#define vkGetPhysicalDeviceSurfaceSupportKHR \
|
||||
pfnGetPhysicalDeviceSurfaceSupportKHR
|
||||
#define vkGetPhysicalDeviceSurfaceCapabilitiesKHR \
|
||||
pfnGetPhysicalDeviceSurfaceCapabilitiesKHR
|
||||
#define vkGetPhysicalDeviceSurfaceFormatsKHR \
|
||||
pfnGetPhysicalDeviceSurfaceFormatsKHR
|
||||
#define vkGetPhysicalDeviceSurfacePresentModesKHR \
|
||||
pfnGetPhysicalDeviceSurfacePresentModesKHR
|
||||
|
||||
#define vkCreateSwapchainKHR pfnCreateSwapchainKHR
|
||||
#define vkDestroySwapchainKHR pfnDestroySwapchainKHR
|
||||
#define vkGetSwapchainImagesKHR pfnGetSwapchainImagesKHR
|
||||
#define vkAcquireNextImageKHR pfnAcquireNextImageKHR
|
||||
#define vkQueuePresentKHR pfnQueuePresentKHR
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
<!-- Copyright 2024 Mark Callow -->
|
||||
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
||||
|
||||
# Vulkan ICD and Explicit Layer Manifest Files
|
||||
|
||||
These were copied from `macOS/share/vulkan` in the Vulkan SDK and have been
|
||||
modified for inclusion in a macOS application bundle to cause the Vulkan loader
|
||||
to find the libraries within the bundle.
|
||||
+1709
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"file_format_version": "1.0.0",
|
||||
"ICD": {
|
||||
"library_path": "../../../Frameworks/libMoltenVK.dylib",
|
||||
"api_version": "1.2.0",
|
||||
"is_portability_driver": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2017-2020 Mark Callow
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#version 450 core
|
||||
|
||||
layout (location = 0) in vec2 inUV;
|
||||
|
||||
layout (binding = 0) uniform sampler2D samplerFont;
|
||||
|
||||
layout (location = 0) out vec4 outFragColor;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
float color = texture(samplerFont, inUV).r;
|
||||
outFragColor = vec4(vec3(color), 1.0);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2017-2020 Mark Callow
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#version 450 core
|
||||
|
||||
layout (location = 0) in vec2 inPos;
|
||||
layout (location = 1) in vec2 inUV;
|
||||
|
||||
layout (location = 0) out vec2 outUV;
|
||||
|
||||
out gl_PerVertex
|
||||
{
|
||||
vec4 gl_Position;
|
||||
};
|
||||
|
||||
void main(void)
|
||||
{
|
||||
gl_Position = vec4(inPos, 0.0, 1.0);
|
||||
outUV = inUV;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
#ifndef VULKAN_CHECK_RES_H_1456211188
|
||||
#define VULKAN_CHECK_RES_H_1456211188
|
||||
|
||||
/*
|
||||
* Copyright 2017-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include <SDL3/SDL_messagebox.h>
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @file
|
||||
* @~English
|
||||
*
|
||||
* @brief Check result of a Vulkan command.
|
||||
*
|
||||
* Use for commands that will always succeed unless usage is invalid.
|
||||
*/
|
||||
#if defined(DEBUG)
|
||||
#define VK_CHECK_RESULT(f) \
|
||||
{ \
|
||||
VkResult res = (f); \
|
||||
if (res != VK_SUCCESS) \
|
||||
{ \
|
||||
std::stringstream msg; \
|
||||
msg << "Fatal : VkResult is \"" << res << "\" in " << __FILE__ \
|
||||
<< " at line " << __LINE__ << std::endl; \
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, \
|
||||
"VkSample_02_cube_textured", \
|
||||
msg.str().c_str(), \
|
||||
NULL); \
|
||||
assert(res == VK_SUCCESS); \
|
||||
} \
|
||||
}
|
||||
#else
|
||||
#define VK_CHECK_RESULT(f) (void)f
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* Vulkan examples debug wrapper
|
||||
*
|
||||
* Appendix for VK_EXT_Debug_Report can be found at https://github.com/KhronosGroup/Vulkan-Docs/blob/1.0-VK_EXT_debug_report/doc/specs/vulkan/appendices/debug_report.txt
|
||||
*
|
||||
* Copyright 2016 Sascha Willems - www.saschawillems.de
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "vulkandebug.h"
|
||||
#include <iostream>
|
||||
#include "unused.h"
|
||||
|
||||
namespace vkDebug
|
||||
{
|
||||
int validationLayerCount = 1;
|
||||
const char *validationLayerNames[] =
|
||||
{
|
||||
// This is a meta layer that enables all of the standard
|
||||
// validation layers in the correct order :
|
||||
// threading, parameter_validation, device_limits, object_tracker, image, core_validation, swapchain, and unique_objects
|
||||
"VK_LAYER_LUNARG_standard_validation"
|
||||
};
|
||||
|
||||
PFN_vkCreateDebugReportCallbackEXT CreateDebugReportCallback = VK_NULL_HANDLE;
|
||||
PFN_vkDestroyDebugReportCallbackEXT DestroyDebugReportCallback = VK_NULL_HANDLE;
|
||||
PFN_vkDebugReportMessageEXT dbgBreakCallback = VK_NULL_HANDLE;
|
||||
|
||||
VkDebugReportCallbackEXT msgCallback;
|
||||
|
||||
VkBool32 messageCallback(
|
||||
VkDebugReportFlagsEXT flags,
|
||||
VkDebugReportObjectTypeEXT /*objType*/,
|
||||
uint64_t /*srcObject*/,
|
||||
size_t /*location*/,
|
||||
int32_t msgCode,
|
||||
const char* pLayerPrefix,
|
||||
const char* pMsg,
|
||||
void* /*pUserData*/)
|
||||
{
|
||||
// Message text passed in by validation layer
|
||||
std::string text(pMsg);
|
||||
|
||||
// Select prefix depending on flags passed to the callback
|
||||
// Note that multiple flags may be set for a single validation message
|
||||
std::string prefix("");
|
||||
|
||||
// Error that may result in undefined behaviour
|
||||
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT)
|
||||
{
|
||||
prefix += "ERROR:";
|
||||
};
|
||||
// Warnings may hint at unexpected / non-spec API usage
|
||||
if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT)
|
||||
{
|
||||
prefix += "WARNING:";
|
||||
};
|
||||
// May indicate sub-optimal usage of the API
|
||||
if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT)
|
||||
{
|
||||
prefix += "PERFORMANCE:";
|
||||
};
|
||||
// Informal messages that may become handy during debugging
|
||||
if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT)
|
||||
{
|
||||
prefix += "INFO:";
|
||||
}
|
||||
// Diagnostic info from the Vulkan loader and layers
|
||||
// Usually not helpful in terms of API usage, but may help to debug layer and loader problems
|
||||
if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT)
|
||||
{
|
||||
prefix += "DEBUG:";
|
||||
}
|
||||
|
||||
// Display message to default output (console if activated)
|
||||
std::cout << prefix << " [" << pLayerPrefix << "] Code " << msgCode << " : " << pMsg << "\n";
|
||||
|
||||
fflush(stdout);
|
||||
|
||||
// The return value of this callback controls wether the Vulkan call that caused
|
||||
// the validation message will be aborted or not
|
||||
// We return VK_FALSE as we DON'T want Vulkan calls that cause a validation message
|
||||
// (and return a VkResult) to abort
|
||||
// If you instead want to have calls abort, pass in VK_TRUE and the function will
|
||||
// return VK_ERROR_VALIDATION_FAILED_EXT
|
||||
return VK_FALSE;
|
||||
}
|
||||
|
||||
void setupDebugging(VkInstance instance, VkDebugReportFlagsEXT flags, VkDebugReportCallbackEXT callBack)
|
||||
{
|
||||
CreateDebugReportCallback = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
|
||||
DestroyDebugReportCallback = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT");
|
||||
dbgBreakCallback = (PFN_vkDebugReportMessageEXT)vkGetInstanceProcAddr(instance, "vkDebugReportMessageEXT");
|
||||
|
||||
VkDebugReportCallbackCreateInfoEXT dbgCreateInfo = {};
|
||||
dbgCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
|
||||
dbgCreateInfo.pfnCallback = (PFN_vkDebugReportCallbackEXT)messageCallback;
|
||||
dbgCreateInfo.flags = flags;
|
||||
|
||||
U_ASSERT_ONLY VkResult err = CreateDebugReportCallback(
|
||||
instance,
|
||||
&dbgCreateInfo,
|
||||
nullptr,
|
||||
(callBack != VK_NULL_HANDLE) ? &callBack : &msgCallback);
|
||||
assert(!err);
|
||||
(void)err; // Supress VC++ unused variable warning for Release config.
|
||||
}
|
||||
|
||||
void freeDebugCallback(VkInstance instance)
|
||||
{
|
||||
if (msgCallback != VK_NULL_HANDLE)
|
||||
{
|
||||
DestroyDebugReportCallback(instance, msgCallback, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
namespace DebugMarker
|
||||
{
|
||||
bool active = false;
|
||||
|
||||
PFN_vkDebugMarkerSetObjectTagEXT pfnDebugMarkerSetObjectTag = VK_NULL_HANDLE;
|
||||
PFN_vkDebugMarkerSetObjectNameEXT pfnDebugMarkerSetObjectName = VK_NULL_HANDLE;
|
||||
PFN_vkCmdDebugMarkerBeginEXT pfnCmdDebugMarkerBegin = VK_NULL_HANDLE;
|
||||
PFN_vkCmdDebugMarkerEndEXT pfnCmdDebugMarkerEnd = VK_NULL_HANDLE;
|
||||
PFN_vkCmdDebugMarkerInsertEXT pfnCmdDebugMarkerInsert = VK_NULL_HANDLE;
|
||||
|
||||
void setup(VkDevice device)
|
||||
{
|
||||
pfnDebugMarkerSetObjectTag = (PFN_vkDebugMarkerSetObjectTagEXT)vkGetDeviceProcAddr(device, "vkDebugMarkerSetObjectTagEXT");
|
||||
pfnDebugMarkerSetObjectName = (PFN_vkDebugMarkerSetObjectNameEXT)vkGetDeviceProcAddr(device, "vkDebugMarkerSetObjectNameEXT");
|
||||
pfnCmdDebugMarkerBegin = (PFN_vkCmdDebugMarkerBeginEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerBeginEXT");
|
||||
pfnCmdDebugMarkerEnd = (PFN_vkCmdDebugMarkerEndEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerEndEXT");
|
||||
pfnCmdDebugMarkerInsert = (PFN_vkCmdDebugMarkerInsertEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerInsertEXT");
|
||||
|
||||
// Set flag if at least one function pointer is present
|
||||
active = (pfnDebugMarkerSetObjectName != VK_NULL_HANDLE);
|
||||
}
|
||||
|
||||
void setObjectName(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, const char *name)
|
||||
{
|
||||
// Check for valid function pointer (may not be present if not running in a debugging application)
|
||||
if (pfnDebugMarkerSetObjectName)
|
||||
{
|
||||
VkDebugMarkerObjectNameInfoEXT nameInfo = {};
|
||||
nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT;
|
||||
nameInfo.objectType = objectType;
|
||||
nameInfo.object = object;
|
||||
nameInfo.pObjectName = name;
|
||||
pfnDebugMarkerSetObjectName(device, &nameInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void setObjectTag(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, uint64_t name, size_t tagSize, const void* tag)
|
||||
{
|
||||
// Check for valid function pointer (may not be present if not running in a debugging application)
|
||||
if (pfnDebugMarkerSetObjectTag)
|
||||
{
|
||||
VkDebugMarkerObjectTagInfoEXT tagInfo = {};
|
||||
tagInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_TAG_INFO_EXT;
|
||||
tagInfo.objectType = objectType;
|
||||
tagInfo.object = object;
|
||||
tagInfo.tagName = name;
|
||||
tagInfo.tagSize = tagSize;
|
||||
tagInfo.pTag = tag;
|
||||
pfnDebugMarkerSetObjectTag(device, &tagInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void beginRegion(VkCommandBuffer cmdbuffer, const char* pMarkerName, glm::vec4 color)
|
||||
{
|
||||
// Check for valid function pointer (may not be present if not running in a debugging application)
|
||||
if (pfnCmdDebugMarkerBegin)
|
||||
{
|
||||
VkDebugMarkerMarkerInfoEXT markerInfo = {};
|
||||
markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
|
||||
memcpy(markerInfo.color, &color[0], sizeof(float) * 4);
|
||||
markerInfo.pMarkerName = pMarkerName;
|
||||
pfnCmdDebugMarkerBegin(cmdbuffer, &markerInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void insert(VkCommandBuffer cmdbuffer, std::string markerName, glm::vec4 color)
|
||||
{
|
||||
// Check for valid function pointer (may not be present if not running in a debugging application)
|
||||
if (pfnCmdDebugMarkerInsert)
|
||||
{
|
||||
VkDebugMarkerMarkerInfoEXT markerInfo = {};
|
||||
markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
|
||||
memcpy(markerInfo.color, &color[0], sizeof(float) * 4);
|
||||
markerInfo.pMarkerName = markerName.c_str();
|
||||
pfnCmdDebugMarkerInsert(cmdbuffer, &markerInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void endRegion(VkCommandBuffer cmdBuffer)
|
||||
{
|
||||
// Check for valid function (may not be present if not runnin in a debugging application)
|
||||
if (pfnCmdDebugMarkerEnd)
|
||||
{
|
||||
pfnCmdDebugMarkerEnd(cmdBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
void setCommandBufferName(VkDevice device, VkCommandBuffer cmdBuffer, const char * name)
|
||||
{
|
||||
setObjectName(device, (uint64_t)cmdBuffer, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, name);
|
||||
}
|
||||
|
||||
void setQueueName(VkDevice device, VkQueue queue, const char * name)
|
||||
{
|
||||
setObjectName(device, (uint64_t)queue, VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT, name);
|
||||
}
|
||||
|
||||
void setImageName(VkDevice device, VkImage image, const char * name)
|
||||
{
|
||||
setObjectName(device, (uint64_t)image, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, name);
|
||||
}
|
||||
|
||||
void setSamplerName(VkDevice device, VkSampler sampler, const char * name)
|
||||
{
|
||||
setObjectName(device, (uint64_t)sampler, VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT, name);
|
||||
}
|
||||
|
||||
void setBufferName(VkDevice device, VkBuffer buffer, const char * name)
|
||||
{
|
||||
setObjectName(device, (uint64_t)buffer, VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, name);
|
||||
}
|
||||
|
||||
void setDeviceMemoryName(VkDevice device, VkDeviceMemory memory, const char * name)
|
||||
{
|
||||
setObjectName(device, (uint64_t)memory, VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT, name);
|
||||
}
|
||||
|
||||
void setShaderModuleName(VkDevice device, VkShaderModule shaderModule, const char * name)
|
||||
{
|
||||
setObjectName(device, (uint64_t)shaderModule, VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT, name);
|
||||
}
|
||||
|
||||
void setPipelineName(VkDevice device, VkPipeline pipeline, const char * name)
|
||||
{
|
||||
setObjectName(device, (uint64_t)pipeline, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT, name);
|
||||
}
|
||||
|
||||
void setPipelineLayoutName(VkDevice device, VkPipelineLayout pipelineLayout, const char * name)
|
||||
{
|
||||
setObjectName(device, (uint64_t)pipelineLayout, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT, name);
|
||||
}
|
||||
|
||||
void setRenderPassName(VkDevice device, VkRenderPass renderPass, const char * name)
|
||||
{
|
||||
setObjectName(device, (uint64_t)renderPass, VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT, name);
|
||||
}
|
||||
|
||||
void setFramebufferName(VkDevice device, VkFramebuffer framebuffer, const char * name)
|
||||
{
|
||||
setObjectName(device, (uint64_t)framebuffer, VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT, name);
|
||||
}
|
||||
|
||||
void setDescriptorSetLayoutName(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const char * name)
|
||||
{
|
||||
setObjectName(device, (uint64_t)descriptorSetLayout, VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT, name);
|
||||
}
|
||||
|
||||
void setDescriptorSetName(VkDevice device, VkDescriptorSet descriptorSet, const char * name)
|
||||
{
|
||||
setObjectName(device, (uint64_t)descriptorSet, VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT, name);
|
||||
}
|
||||
|
||||
void setSemaphoreName(VkDevice device, VkSemaphore semaphore, const char * name)
|
||||
{
|
||||
setObjectName(device, (uint64_t)semaphore, VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT, name);
|
||||
}
|
||||
|
||||
void setFenceName(VkDevice device, VkFence fence, const char * name)
|
||||
{
|
||||
setObjectName(device, (uint64_t)fence, VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT, name);
|
||||
}
|
||||
|
||||
void setEventName(VkDevice device, VkEvent _event, const char * name)
|
||||
{
|
||||
setObjectName(device, (uint64_t)_event, VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT, name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vi: set sw=2 ts=4 expandtab:
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2017-2020 Mark Callow
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "vulkan/vulkan.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#endif
|
||||
|
||||
#define GLM_FORCE_RADIANS
|
||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||
#include "disable_glm_warnings.h"
|
||||
#include <glm/glm.hpp>
|
||||
#include "reenable_warnings.h"
|
||||
|
||||
namespace vkDebug
|
||||
{
|
||||
// Default validation layers
|
||||
extern int validationLayerCount;
|
||||
extern const char *validationLayerNames[];
|
||||
|
||||
// Default debug callback
|
||||
VkBool32 messageCallback(
|
||||
VkDebugReportFlagsEXT flags,
|
||||
VkDebugReportObjectTypeEXT objType,
|
||||
uint64_t srcObject,
|
||||
size_t location,
|
||||
int32_t msgCode,
|
||||
const char* pLayerPrefix,
|
||||
const char* pMsg,
|
||||
void* pUserData);
|
||||
|
||||
// Load debug function pointers and set debug callback
|
||||
// if callBack is NULL, default message callback will be used
|
||||
void setupDebugging(
|
||||
VkInstance instance,
|
||||
VkDebugReportFlagsEXT flags,
|
||||
VkDebugReportCallbackEXT callBack);
|
||||
// Clear debug callback
|
||||
void freeDebugCallback(VkInstance instance);
|
||||
|
||||
// Setup and functions for the VK_EXT_debug_marker_extension
|
||||
// Extension spec can be found at https://github.com/KhronosGroup/Vulkan-Docs/blob/1.0-VK_EXT_debug_marker/doc/specs/vulkan/appendices/VK_EXT_debug_marker.txt
|
||||
// Note that the extension will only be present if run from an offline debugging application
|
||||
// The actual check for extension presence and enabling it on the device is done in the example base class
|
||||
// See VulkanExampleBase::createInstance and VulkanExampleBase::createDevice (base/vulkanexamplebase.cpp)
|
||||
namespace DebugMarker
|
||||
{
|
||||
// Set to true if function pointer for the debug marker are available
|
||||
extern bool active;
|
||||
|
||||
// Get function pointers for the debug report extensions from the device
|
||||
void setup(VkDevice device);
|
||||
|
||||
// Sets the debug name of an object
|
||||
// All Objects in Vulkan are represented by their 64-bit handles which are passed into this function
|
||||
// along with the object type
|
||||
void setObjectName(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, const char *name);
|
||||
|
||||
// Set the tag for an object
|
||||
void setObjectTag(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, uint64_t name, size_t tagSize, const void* tag);
|
||||
|
||||
// Start a new debug marker region
|
||||
void beginRegion(VkCommandBuffer cmdbuffer, const char* pMarkerName, glm::vec4 color);
|
||||
|
||||
// Insert a new debug marker into the command buffer
|
||||
void insert(VkCommandBuffer cmdbuffer, std::string markerName, glm::vec4 color);
|
||||
|
||||
// End the current debug marker region
|
||||
void endRegion(VkCommandBuffer cmdBuffer);
|
||||
|
||||
// Object specific naming functions
|
||||
void setCommandBufferName(VkDevice device, VkCommandBuffer cmdBuffer, const char * name);
|
||||
void setQueueName(VkDevice device, VkQueue queue, const char * name);
|
||||
void setImageName(VkDevice device, VkImage image, const char * name);
|
||||
void setSamplerName(VkDevice device, VkSampler sampler, const char * name);
|
||||
void setBufferName(VkDevice device, VkBuffer buffer, const char * name);
|
||||
void setDeviceMemoryName(VkDevice device, VkDeviceMemory memory, const char * name);
|
||||
void setShaderModuleName(VkDevice device, VkShaderModule shaderModule, const char * name);
|
||||
void setPipelineName(VkDevice device, VkPipeline pipeline, const char * name);
|
||||
void setPipelineLayoutName(VkDevice device, VkPipelineLayout pipelineLayout, const char * name);
|
||||
void setRenderPassName(VkDevice device, VkRenderPass renderPass, const char * name);
|
||||
void setFramebufferName(VkDevice device, VkFramebuffer framebuffer, const char * name);
|
||||
void setDescriptorSetLayoutName(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const char * name);
|
||||
void setDescriptorSetName(VkDevice device, VkDescriptorSet descriptorSet, const char * name);
|
||||
void setSemaphoreName(VkDevice device, VkSemaphore semaphore, const char * name);
|
||||
void setFenceName(VkDevice device, VkFence fence, const char * name);
|
||||
void setEventName(VkDevice device, VkEvent _event, const char * name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vi: set sw=2 ts=4 expandtab:
|
||||
@@ -0,0 +1,839 @@
|
||||
/*
|
||||
* Copyright 2016 Sascha Willems - www.saschawillems.de
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Text overlay class for displaying debug information
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
#include "vulkantools.h"
|
||||
#include "vulkandebug.h"
|
||||
|
||||
#include "stb/stb_font_consolas_24_latin1.inl"
|
||||
|
||||
// Defines for the STB font used
|
||||
// STB font files can be found at http://nothings.org/stb/font/
|
||||
#define STB_FONT_NAME stb_font_consolas_24_latin1
|
||||
#define STB_FONT_WIDTH STB_FONT_consolas_24_latin1_BITMAP_WIDTH
|
||||
#define STB_FONT_HEIGHT STB_FONT_consolas_24_latin1_BITMAP_HEIGHT
|
||||
#define STB_FIRST_CHAR STB_FONT_consolas_24_latin1_FIRST_CHAR
|
||||
#define STB_NUM_CHARS STB_FONT_consolas_24_latin1_NUM_CHARS
|
||||
#define STB_MISSING_GLPYH 0x80 // Actually a control character.
|
||||
|
||||
// Max. number of chars the text overlay buffer can hold
|
||||
#define MAX_CHAR_COUNT 1024
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @brief Given the lead byte of a UTF-8 sequence returns the expected length of the codepoint
|
||||
* @param[in] leadByte The lead byte of a UTF-8 sequence
|
||||
* @return The expected length of the codepoint */
|
||||
[[nodiscard]] constexpr inline int sequenceLength(uint8_t leadByte) noexcept {
|
||||
if ((leadByte & 0b1000'0000u) == 0b0000'0000u)
|
||||
return 1;
|
||||
if ((leadByte & 0b1110'0000u) == 0b1100'0000u)
|
||||
return 2;
|
||||
if ((leadByte & 0b1111'0000u) == 0b1110'0000u)
|
||||
return 3;
|
||||
if ((leadByte & 0b1111'1000u) == 0b1111'0000u)
|
||||
return 4;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @brief Checks if the codepoint was coded as a longer than required sequence
|
||||
* @param[in] codepoint The unicode codepoint
|
||||
* @param[in] length The UTF-8 sequence length
|
||||
* @return True if the sequence length was inappropriate for the given codepoint */
|
||||
[[nodiscard]] constexpr inline bool isOverlongSequence(uint32_t codepoint, int length) noexcept {
|
||||
if (codepoint < 0x80)
|
||||
return length != 1;
|
||||
else if (codepoint < 0x800)
|
||||
return length != 2;
|
||||
else if (codepoint < 0x10000)
|
||||
return length != 3;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @brief Checks if the codepoint is valid
|
||||
* @param[in] codepoint The unicode codepoint
|
||||
* @return True if the codepoint is a valid unicode codepoint */
|
||||
[[nodiscard]] constexpr inline bool isCodepointValid(uint32_t codepoint) noexcept {
|
||||
return codepoint <= 0x0010FFFFu
|
||||
&& !(0xD800u <= codepoint && codepoint <= 0xDBFFu);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @brief Safely checks and advances a UTF-8 sequence iterator to the start of the next unicode codepoint
|
||||
* @param[in] it iterator to be advanced
|
||||
* @param[in] end iterator pointing to the end of the range
|
||||
* @return True if the advance operation was successful and the advanced codepoint was a valid UTF-8 sequence */
|
||||
template <typename Iterator>
|
||||
[[nodiscard]] constexpr bool advanceUTF8(Iterator& it, Iterator end,
|
||||
uint32_t& codepoint) noexcept {
|
||||
if (it == end)
|
||||
return false;
|
||||
|
||||
const auto length = sequenceLength(*it);
|
||||
if (length == 0)
|
||||
return false;
|
||||
|
||||
if (std::distance(it, end) < length)
|
||||
return false;
|
||||
|
||||
for (int i = 1; i < length; ++i) {
|
||||
const auto trailByte = *(it + i);
|
||||
if ((static_cast<uint8_t>(trailByte) & 0b1100'0000u) != 0b1000'0000u)
|
||||
return false;
|
||||
}
|
||||
|
||||
codepoint = 0;
|
||||
switch (length) {
|
||||
case 1:
|
||||
codepoint |= *it++;
|
||||
break;
|
||||
case 2:
|
||||
codepoint |= (*it++ & 0b0001'1111u) << 6u;
|
||||
codepoint |= (*it++ & 0b0011'1111u);
|
||||
break;
|
||||
case 3:
|
||||
codepoint |= (*it++ & 0b0000'1111u) << 12u;
|
||||
codepoint |= (*it++ & 0b0011'1111u) << 6u;
|
||||
codepoint |= (*it++ & 0b0011'1111u);
|
||||
break;
|
||||
case 4:
|
||||
codepoint |= (*it++ & 0b0000'0111u) << 18u;
|
||||
codepoint |= (*it++ & 0b0011'1111u) << 12u;
|
||||
codepoint |= (*it++ & 0b0011'1111u) << 6u;
|
||||
codepoint |= (*it++ & 0b0011'1111u);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isCodepointValid(codepoint))
|
||||
return false;
|
||||
|
||||
if (isOverlongSequence(codepoint, length))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Mostly self-contained text overlay class
|
||||
// todo : comment
|
||||
class VulkanTextOverlay
|
||||
{
|
||||
private:
|
||||
VkPhysicalDevice physicalDevice;
|
||||
VkDevice device;
|
||||
VkPhysicalDeviceMemoryProperties deviceMemoryProperties;
|
||||
VkQueue queue;
|
||||
VkFormat colorFormat;
|
||||
VkFormat depthFormat;
|
||||
|
||||
uint32_t *frameBufferWidth;
|
||||
uint32_t *frameBufferHeight;
|
||||
|
||||
VkSampler sampler;
|
||||
VkImage image;
|
||||
VkImageView view;
|
||||
VkBuffer buffer;
|
||||
VkDeviceMemory memory;
|
||||
VkDeviceMemory imageMemory;
|
||||
VkDescriptorPool descriptorPool;
|
||||
VkDescriptorSetLayout descriptorSetLayout;
|
||||
VkDescriptorSet descriptorSet;
|
||||
VkPipelineLayout pipelineLayout;
|
||||
VkPipelineCache pipelineCache;
|
||||
VkPipeline pipeline;
|
||||
VkRenderPass renderPass;
|
||||
VkCommandPool commandPool;
|
||||
std::vector<VkFramebuffer*> frameBuffers;
|
||||
std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
|
||||
|
||||
// Pointer to mapped vertex buffer
|
||||
glm::vec4 *mapped = nullptr;
|
||||
// Used during text updates
|
||||
glm::vec4 *mappedLocal = nullptr;
|
||||
|
||||
stb_fontchar stbFontData[STB_NUM_CHARS];
|
||||
uint32_t numLetters;
|
||||
|
||||
// Try to find appropriate memory type for a memory allocation
|
||||
uint32_t getMemoryType(uint32_t typeBits, VkFlags properties)
|
||||
{
|
||||
for (uint32_t i = 0; i < 32; i++)
|
||||
{
|
||||
if ((typeBits & 1) == 1)
|
||||
{
|
||||
if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
typeBits >>= 1;
|
||||
}
|
||||
|
||||
// todo : throw error
|
||||
return 0;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
enum TextAlign { alignLeft, alignCenter, alignRight };
|
||||
|
||||
bool visible = true;
|
||||
bool invalidated = false;
|
||||
|
||||
std::vector<VkCommandBuffer> cmdBuffers;
|
||||
|
||||
VulkanTextOverlay(
|
||||
VkPhysicalDevice physicalDevice,
|
||||
VkDevice device,
|
||||
VkQueue queue,
|
||||
std::vector<VkFramebuffer> &framebuffers,
|
||||
VkFormat colorformat,
|
||||
VkFormat depthformat,
|
||||
uint32_t *framebufferwidth,
|
||||
uint32_t *framebufferheight,
|
||||
std::vector<VkPipelineShaderStageCreateInfo> shaderstages)
|
||||
{
|
||||
this->physicalDevice = physicalDevice;
|
||||
this->device = device;
|
||||
this->queue = queue;
|
||||
this->colorFormat = colorformat;
|
||||
this->depthFormat = depthformat;
|
||||
|
||||
this->frameBuffers.resize(framebuffers.size());
|
||||
for (uint32_t i = 0; i < framebuffers.size(); i++)
|
||||
{
|
||||
this->frameBuffers[i] = &framebuffers[i];
|
||||
}
|
||||
|
||||
this->shaderStages = shaderstages;
|
||||
|
||||
this->frameBufferWidth = framebufferwidth;
|
||||
this->frameBufferHeight = framebufferheight;
|
||||
|
||||
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties);
|
||||
cmdBuffers.resize(framebuffers.size());
|
||||
prepareResources();
|
||||
prepareRenderPass();
|
||||
preparePipeline();
|
||||
}
|
||||
|
||||
~VulkanTextOverlay()
|
||||
{
|
||||
// Free up all Vulkan resources requested by the text overlay
|
||||
vkDestroySampler(device, sampler, nullptr);
|
||||
vkDestroyImage(device, image, nullptr);
|
||||
vkDestroyImageView(device, view, nullptr);
|
||||
vkDestroyBuffer(device, buffer, nullptr);
|
||||
vkFreeMemory(device, memory, nullptr);
|
||||
vkFreeMemory(device, imageMemory, nullptr);
|
||||
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
|
||||
vkDestroyDescriptorPool(device, descriptorPool, nullptr);
|
||||
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
|
||||
vkDestroyPipelineCache(device, pipelineCache, nullptr);
|
||||
vkDestroyPipeline(device, pipeline, nullptr);
|
||||
vkDestroyRenderPass(device, renderPass, nullptr);
|
||||
vkFreeCommandBuffers(device, commandPool, static_cast<uint32_t>(cmdBuffers.size()), cmdBuffers.data());
|
||||
vkDestroyCommandPool(device, commandPool, nullptr);
|
||||
}
|
||||
|
||||
// Prepare all vulkan resources required to render the font
|
||||
// The text overlay uses separate resources for descriptors (pool, sets, layouts), pipelines and command buffers
|
||||
void prepareResources()
|
||||
{
|
||||
static unsigned char font24pixels[STB_FONT_HEIGHT][STB_FONT_WIDTH];
|
||||
STB_FONT_NAME(stbFontData, font24pixels, STB_FONT_HEIGHT);
|
||||
|
||||
// Command buffer
|
||||
|
||||
// Pool
|
||||
VkCommandPoolCreateInfo cmdPoolInfo = {};
|
||||
cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
cmdPoolInfo.queueFamilyIndex = 0; // todo : pass from example base / swap chain
|
||||
cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||
VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &commandPool));
|
||||
|
||||
VkCommandBufferAllocateInfo cmdBufAllocateInfo =
|
||||
vkTools::initializers::commandBufferAllocateInfo(
|
||||
commandPool,
|
||||
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
(uint32_t)cmdBuffers.size());
|
||||
|
||||
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, cmdBuffers.data()));
|
||||
|
||||
// Vertex buffer, 4 per character.
|
||||
VkDeviceSize bufferSize = MAX_CHAR_COUNT * sizeof(glm::vec4) * 4;
|
||||
|
||||
VkBufferCreateInfo bufferInfo = vkTools::initializers::bufferCreateInfo(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, bufferSize);
|
||||
VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo, nullptr, &buffer));
|
||||
|
||||
VkMemoryRequirements memReqs;
|
||||
VkMemoryAllocateInfo allocInfo = vkTools::initializers::memoryAllocateInfo();
|
||||
|
||||
vkGetBufferMemoryRequirements(device, buffer, &memReqs);
|
||||
allocInfo.allocationSize = memReqs.size;
|
||||
allocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &memory));
|
||||
VK_CHECK_RESULT(vkBindBufferMemory(device, buffer, memory, 0));
|
||||
|
||||
// Map persistent
|
||||
VK_CHECK_RESULT(vkMapMemory(device, memory, 0, VK_WHOLE_SIZE, 0, (void **)&mapped));
|
||||
|
||||
|
||||
// Font texture
|
||||
VkImageCreateInfo imageInfo = vkTools::initializers::imageCreateInfo();
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imageInfo.format = VK_FORMAT_R8_UNORM;
|
||||
imageInfo.extent.width = STB_FONT_WIDTH;
|
||||
imageInfo.extent.height = STB_FONT_HEIGHT;
|
||||
imageInfo.extent.depth = 1;
|
||||
imageInfo.mipLevels = 1;
|
||||
imageInfo.arrayLayers = 1;
|
||||
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
|
||||
VK_CHECK_RESULT(vkCreateImage(device, &imageInfo, nullptr, &image));
|
||||
|
||||
vkGetImageMemoryRequirements(device, image, &memReqs);
|
||||
allocInfo.allocationSize = memReqs.size;
|
||||
allocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory));
|
||||
VK_CHECK_RESULT(vkBindImageMemory(device, image, imageMemory, 0));
|
||||
|
||||
// Staging
|
||||
|
||||
struct {
|
||||
VkDeviceMemory memory;
|
||||
VkBuffer buffer;
|
||||
} stagingBuffer;
|
||||
|
||||
VkBufferCreateInfo bufferCreateInfo = vkTools::initializers::bufferCreateInfo();
|
||||
bufferCreateInfo.size = allocInfo.allocationSize;
|
||||
bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
|
||||
VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &stagingBuffer.buffer));
|
||||
|
||||
// Get memory requirements for the staging buffer (alignment, memory type bits)
|
||||
vkGetBufferMemoryRequirements(device, stagingBuffer.buffer, &memReqs);
|
||||
|
||||
allocInfo.allocationSize = memReqs.size;
|
||||
// Get memory type index for a host visible buffer
|
||||
allocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
|
||||
|
||||
VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &stagingBuffer.memory));
|
||||
VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffer.buffer, stagingBuffer.memory, 0));
|
||||
|
||||
uint8_t *data;
|
||||
VK_CHECK_RESULT(vkMapMemory(device, stagingBuffer.memory, 0, allocInfo.allocationSize, 0, (void **)&data));
|
||||
memcpy(data, &font24pixels[0][0], STB_FONT_WIDTH * STB_FONT_HEIGHT);
|
||||
vkUnmapMemory(device, stagingBuffer.memory);
|
||||
|
||||
// Copy to image
|
||||
|
||||
VkCommandBuffer copyCmd;
|
||||
cmdBufAllocateInfo.commandBufferCount = 1;
|
||||
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, ©Cmd));
|
||||
|
||||
VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo();
|
||||
VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo));
|
||||
|
||||
// Prepare for transfer
|
||||
vkTools::setImageLayout(
|
||||
copyCmd,
|
||||
image,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
VK_IMAGE_LAYOUT_PREINITIALIZED,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
|
||||
VkBufferImageCopy bufferCopyRegion = {};
|
||||
bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
bufferCopyRegion.imageSubresource.mipLevel = 0;
|
||||
bufferCopyRegion.imageSubresource.layerCount = 1;
|
||||
bufferCopyRegion.imageExtent.width = STB_FONT_WIDTH;
|
||||
bufferCopyRegion.imageExtent.height = STB_FONT_HEIGHT;
|
||||
bufferCopyRegion.imageExtent.depth = 1;
|
||||
|
||||
vkCmdCopyBufferToImage(
|
||||
copyCmd,
|
||||
stagingBuffer.buffer,
|
||||
image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
1,
|
||||
&bufferCopyRegion
|
||||
);
|
||||
|
||||
// Prepare for shader read
|
||||
vkTools::setImageLayout(
|
||||
copyCmd,
|
||||
image,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
|
||||
VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd));
|
||||
|
||||
VkSubmitInfo submitInfo = vkTools::initializers::submitInfo();
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = ©Cmd;
|
||||
|
||||
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
|
||||
VK_CHECK_RESULT(vkQueueWaitIdle(queue));
|
||||
|
||||
vkFreeCommandBuffers(device, commandPool, 1, ©Cmd);
|
||||
vkFreeMemory(device, stagingBuffer.memory, nullptr);
|
||||
vkDestroyBuffer(device, stagingBuffer.buffer, nullptr);
|
||||
|
||||
|
||||
VkImageViewCreateInfo imageViewInfo = vkTools::initializers::imageViewCreateInfo();
|
||||
imageViewInfo.image = image;
|
||||
imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
imageViewInfo.format = imageInfo.format;
|
||||
imageViewInfo.components = {
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY
|
||||
};
|
||||
imageViewInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
|
||||
|
||||
VK_CHECK_RESULT(vkCreateImageView(device, &imageViewInfo, nullptr, &view));
|
||||
|
||||
// Sampler
|
||||
VkSamplerCreateInfo samplerInfo = vkTools::initializers::samplerCreateInfo();
|
||||
samplerInfo.magFilter = VK_FILTER_LINEAR;
|
||||
samplerInfo.minFilter = VK_FILTER_LINEAR;
|
||||
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
samplerInfo.mipLodBias = 0.0f;
|
||||
samplerInfo.compareOp = VK_COMPARE_OP_NEVER;
|
||||
samplerInfo.minLod = 0.0f;
|
||||
samplerInfo.maxLod = 1.0f;
|
||||
samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||
samplerInfo.anisotropyEnable = VK_FALSE;
|
||||
samplerInfo.maxAnisotropy = 1.0;
|
||||
VK_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &sampler));
|
||||
|
||||
// Descriptor
|
||||
// Font uses a separate descriptor pool
|
||||
std::array<VkDescriptorPoolSize, 1> poolSizes;
|
||||
poolSizes[0] = vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1);
|
||||
|
||||
VkDescriptorPoolCreateInfo descriptorPoolInfo =
|
||||
vkTools::initializers::descriptorPoolCreateInfo(
|
||||
static_cast<uint32_t>(poolSizes.size()),
|
||||
poolSizes.data(),
|
||||
1);
|
||||
|
||||
VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
|
||||
|
||||
// Descriptor set layout
|
||||
std::array<VkDescriptorSetLayoutBinding, 1> setLayoutBindings;
|
||||
setLayoutBindings[0] = vkTools::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0);
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo =
|
||||
vkTools::initializers::descriptorSetLayoutCreateInfo(
|
||||
setLayoutBindings.data(),
|
||||
static_cast<uint32_t>(setLayoutBindings.size()));
|
||||
|
||||
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutInfo, nullptr, &descriptorSetLayout));
|
||||
|
||||
// Pipeline layout
|
||||
VkPipelineLayoutCreateInfo pipelineLayoutInfo =
|
||||
vkTools::initializers::pipelineLayoutCreateInfo(
|
||||
&descriptorSetLayout,
|
||||
1);
|
||||
|
||||
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout));
|
||||
|
||||
// Descriptor set
|
||||
VkDescriptorSetAllocateInfo descriptorSetAllocInfo =
|
||||
vkTools::initializers::descriptorSetAllocateInfo(
|
||||
descriptorPool,
|
||||
&descriptorSetLayout,
|
||||
1);
|
||||
|
||||
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocInfo, &descriptorSet));
|
||||
|
||||
VkDescriptorImageInfo texDescriptor =
|
||||
vkTools::initializers::descriptorImageInfo(
|
||||
sampler,
|
||||
view,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
|
||||
std::array<VkWriteDescriptorSet, 1> writeDescriptorSets;
|
||||
writeDescriptorSets[0] = vkTools::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &texDescriptor);
|
||||
vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
|
||||
|
||||
// Pipeline cache
|
||||
VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {};
|
||||
pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
|
||||
VK_CHECK_RESULT(vkCreatePipelineCache(device, &pipelineCacheCreateInfo, nullptr, &pipelineCache));
|
||||
}
|
||||
|
||||
// Prepare a separate pipeline for the font rendering decoupled from the main application
|
||||
void preparePipeline()
|
||||
{
|
||||
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
|
||||
vkTools::initializers::pipelineInputAssemblyStateCreateInfo(
|
||||
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
|
||||
0,
|
||||
// primmitiveRestartEnable not needed but disabling it results in a MoltenVK
|
||||
// feature not present warning.
|
||||
VK_TRUE);
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo rasterizationState =
|
||||
vkTools::initializers::pipelineRasterizationStateCreateInfo(
|
||||
VK_POLYGON_MODE_FILL,
|
||||
VK_CULL_MODE_BACK_BIT,
|
||||
VK_FRONT_FACE_CLOCKWISE,
|
||||
0);
|
||||
// Because we haven't enabled the depthClamp device feature.
|
||||
rasterizationState.depthClampEnable = VK_FALSE;
|
||||
|
||||
// Enable blending
|
||||
VkPipelineColorBlendAttachmentState blendAttachmentState =
|
||||
vkTools::initializers::pipelineColorBlendAttachmentState(0xf, VK_TRUE);
|
||||
|
||||
blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo colorBlendState =
|
||||
vkTools::initializers::pipelineColorBlendStateCreateInfo(
|
||||
1,
|
||||
&blendAttachmentState);
|
||||
|
||||
VkPipelineDepthStencilStateCreateInfo depthStencilState =
|
||||
vkTools::initializers::pipelineDepthStencilStateCreateInfo(
|
||||
VK_FALSE,
|
||||
VK_FALSE,
|
||||
VK_COMPARE_OP_LESS_OR_EQUAL);
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewportState =
|
||||
vkTools::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo multisampleState =
|
||||
vkTools::initializers::pipelineMultisampleStateCreateInfo(
|
||||
VK_SAMPLE_COUNT_1_BIT,
|
||||
0);
|
||||
|
||||
std::vector<VkDynamicState> dynamicStateEnables = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
VkPipelineDynamicStateCreateInfo dynamicState =
|
||||
vkTools::initializers::pipelineDynamicStateCreateInfo(
|
||||
dynamicStateEnables.data(),
|
||||
static_cast<uint32_t>(dynamicStateEnables.size()),
|
||||
0);
|
||||
|
||||
std::array<VkVertexInputBindingDescription, 2> vertexBindings = {};
|
||||
vertexBindings[0] = vkTools::initializers::vertexInputBindingDescription(0, sizeof(glm::vec4), VK_VERTEX_INPUT_RATE_VERTEX);
|
||||
vertexBindings[1] = vkTools::initializers::vertexInputBindingDescription(1, sizeof(glm::vec4), VK_VERTEX_INPUT_RATE_VERTEX);
|
||||
|
||||
std::array<VkVertexInputAttributeDescription, 2> vertexAttribs = {};
|
||||
// Position
|
||||
vertexAttribs[0] = vkTools::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32_SFLOAT, 0);
|
||||
// UV
|
||||
vertexAttribs[1] = vkTools::initializers::vertexInputAttributeDescription(1, 1, VK_FORMAT_R32G32_SFLOAT, sizeof(glm::vec2));
|
||||
|
||||
VkPipelineVertexInputStateCreateInfo inputState = vkTools::initializers::pipelineVertexInputStateCreateInfo();
|
||||
inputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexBindings.size());
|
||||
inputState.pVertexBindingDescriptions = vertexBindings.data();
|
||||
inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexAttribs.size());
|
||||
inputState.pVertexAttributeDescriptions = vertexAttribs.data();
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipelineCreateInfo =
|
||||
vkTools::initializers::pipelineCreateInfo(
|
||||
pipelineLayout,
|
||||
renderPass,
|
||||
0);
|
||||
|
||||
pipelineCreateInfo.pVertexInputState = &inputState;
|
||||
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
|
||||
pipelineCreateInfo.pRasterizationState = &rasterizationState;
|
||||
pipelineCreateInfo.pColorBlendState = &colorBlendState;
|
||||
pipelineCreateInfo.pMultisampleState = &multisampleState;
|
||||
pipelineCreateInfo.pViewportState = &viewportState;
|
||||
pipelineCreateInfo.pDepthStencilState = &depthStencilState;
|
||||
pipelineCreateInfo.pDynamicState = &dynamicState;
|
||||
pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
|
||||
pipelineCreateInfo.pStages = shaderStages.data();
|
||||
|
||||
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
|
||||
}
|
||||
|
||||
// Prepare a separate render pass for rendering the text as an overlay
|
||||
void prepareRenderPass()
|
||||
{
|
||||
VkAttachmentDescription attachments[2] = {};
|
||||
|
||||
// Color attachment
|
||||
attachments[0].format = colorFormat;
|
||||
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
// Don't clear the framebuffer (like the renderpass from the example does)
|
||||
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[0].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
|
||||
// Depth attachment
|
||||
attachments[1].format = depthFormat;
|
||||
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkAttachmentReference colorReference = {};
|
||||
colorReference.attachment = 0;
|
||||
colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkAttachmentReference depthReference = {};
|
||||
depthReference.attachment = 1;
|
||||
depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkSubpassDescription subpass = {};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.flags = 0;
|
||||
subpass.inputAttachmentCount = 0;
|
||||
subpass.pInputAttachments = NULL;
|
||||
subpass.colorAttachmentCount = 1;
|
||||
subpass.pColorAttachments = &colorReference;
|
||||
subpass.pResolveAttachments = NULL;
|
||||
subpass.pDepthStencilAttachment = &depthReference;
|
||||
subpass.preserveAttachmentCount = 0;
|
||||
subpass.pPreserveAttachments = NULL;
|
||||
|
||||
VkRenderPassCreateInfo renderPassInfo = {};
|
||||
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
renderPassInfo.pNext = NULL;
|
||||
renderPassInfo.attachmentCount = 2;
|
||||
renderPassInfo.pAttachments = attachments;
|
||||
renderPassInfo.subpassCount = 1;
|
||||
renderPassInfo.pSubpasses = &subpass;
|
||||
renderPassInfo.dependencyCount = 0;
|
||||
renderPassInfo.pDependencies = NULL;
|
||||
|
||||
VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass));
|
||||
}
|
||||
|
||||
// Map buffer
|
||||
void beginTextUpdate()
|
||||
{
|
||||
mappedLocal = mapped;
|
||||
numLetters = 0;
|
||||
}
|
||||
|
||||
// Add text to the current buffer
|
||||
// todo : drop shadow? color attribute?
|
||||
void addText(std::string text, float x, float y, TextAlign align)
|
||||
{
|
||||
if (numLetters == MAX_CHAR_COUNT)
|
||||
return;
|
||||
|
||||
assert(mapped != nullptr);
|
||||
|
||||
const float charW = 1.5f / *frameBufferWidth;
|
||||
const float charH = 1.5f / *frameBufferHeight;
|
||||
|
||||
float fbW = (float)*frameBufferWidth;
|
||||
float fbH = (float)*frameBufferHeight;
|
||||
x = (x / fbW * 2.0f) - 1.0f;
|
||||
y = (y / fbH * 2.0f) - 1.0f;
|
||||
|
||||
// Calculate text width
|
||||
float textWidth = 0;
|
||||
uint32_t codepoint;
|
||||
auto it = text.begin();
|
||||
while (it != text.end())
|
||||
{
|
||||
if (!advanceUTF8(it, text.end(), codepoint))
|
||||
break;
|
||||
// TODO: Get a UTF8 font. Consider changing to Dear ImGUI
|
||||
// https://github.com/ocornut/imgui.
|
||||
// Placeholder to avoid crashing.
|
||||
if (codepoint > STB_NUM_CHARS + STB_FIRST_CHAR)
|
||||
codepoint = STB_MISSING_GLPYH;
|
||||
stb_fontchar *charData = &stbFontData[(uint32_t)codepoint - STB_FIRST_CHAR];
|
||||
textWidth += charData->advance * charW;
|
||||
}
|
||||
switch (align)
|
||||
{
|
||||
case alignRight:
|
||||
x -= textWidth;
|
||||
break;
|
||||
case alignCenter:
|
||||
x -= textWidth / 2.0f;
|
||||
break;
|
||||
case alignLeft:
|
||||
break;
|
||||
}
|
||||
|
||||
// Generate a uv mapped quad per char in the new text
|
||||
it = text.begin();
|
||||
while (it != text.end())
|
||||
{
|
||||
if (!advanceUTF8(it, text.end(), codepoint))
|
||||
break;
|
||||
if (codepoint > STB_NUM_CHARS + STB_FIRST_CHAR)
|
||||
codepoint = STB_MISSING_GLPYH;
|
||||
|
||||
stb_fontchar *charData = &stbFontData[(uint32_t)codepoint - STB_FIRST_CHAR];
|
||||
|
||||
mappedLocal->x = (x + (float)charData->x0 * charW);
|
||||
mappedLocal->y = (y + (float)charData->y0 * charH);
|
||||
mappedLocal->z = charData->s0;
|
||||
mappedLocal->w = charData->t0;
|
||||
mappedLocal++;
|
||||
|
||||
mappedLocal->x = (x + (float)charData->x1 * charW);
|
||||
mappedLocal->y = (y + (float)charData->y0 * charH);
|
||||
mappedLocal->z = charData->s1;
|
||||
mappedLocal->w = charData->t0;
|
||||
mappedLocal++;
|
||||
|
||||
mappedLocal->x = (x + (float)charData->x0 * charW);
|
||||
mappedLocal->y = (y + (float)charData->y1 * charH);
|
||||
mappedLocal->z = charData->s0;
|
||||
mappedLocal->w = charData->t1;
|
||||
mappedLocal++;
|
||||
|
||||
mappedLocal->x = (x + (float)charData->x1 * charW);
|
||||
mappedLocal->y = (y + (float)charData->y1 * charH);
|
||||
mappedLocal->z = charData->s1;
|
||||
mappedLocal->w = charData->t1;
|
||||
mappedLocal++;
|
||||
|
||||
x += charData->advance * charW;
|
||||
|
||||
numLetters++;
|
||||
|
||||
if (numLetters == MAX_CHAR_COUNT)
|
||||
break; // Truncate the text.
|
||||
}
|
||||
}
|
||||
// Unmap buffer and update command buffers
|
||||
void endTextUpdate()
|
||||
{
|
||||
updateCommandBuffers();
|
||||
}
|
||||
|
||||
// Needs to be called by the application
|
||||
void updateCommandBuffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo();
|
||||
|
||||
VkRenderPassBeginInfo renderPassBeginInfo = vkTools::initializers::renderPassBeginInfo();
|
||||
renderPassBeginInfo.renderPass = renderPass;
|
||||
renderPassBeginInfo.renderArea.extent.width = *frameBufferWidth;
|
||||
renderPassBeginInfo.renderArea.extent.height = *frameBufferHeight;
|
||||
renderPassBeginInfo.clearValueCount = 0;
|
||||
renderPassBeginInfo.pClearValues = nullptr;
|
||||
|
||||
for (uint32_t i = 0; i < cmdBuffers.size(); ++i)
|
||||
{
|
||||
renderPassBeginInfo.framebuffer = *frameBuffers[i];
|
||||
|
||||
VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffers[i], &cmdBufInfo));
|
||||
|
||||
if (vkDebug::DebugMarker::active)
|
||||
{
|
||||
vkDebug::DebugMarker::beginRegion(cmdBuffers[i], "Text overlay", glm::vec4(1.0f, 0.94f, 0.3f, 1.0f));
|
||||
}
|
||||
|
||||
vkCmdBeginRenderPass(cmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
VkViewport viewport = vkTools::initializers::viewport((float)*frameBufferWidth, (float)*frameBufferHeight, 0.0f, 1.0f);
|
||||
vkCmdSetViewport(cmdBuffers[i], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkTools::initializers::rect2D(*frameBufferWidth, *frameBufferHeight, 0, 0);
|
||||
vkCmdSetScissor(cmdBuffers[i], 0, 1, &scissor);
|
||||
|
||||
vkCmdBindPipeline(cmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
vkCmdBindDescriptorSets(cmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
|
||||
|
||||
VkDeviceSize offsets = 0;
|
||||
vkCmdBindVertexBuffers(cmdBuffers[i], 0, 1, &buffer, &offsets);
|
||||
vkCmdBindVertexBuffers(cmdBuffers[i], 1, 1, &buffer, &offsets);
|
||||
for (uint32_t j = 0; j < numLetters; j++)
|
||||
{
|
||||
vkCmdDraw(cmdBuffers[i], 4, 1, j * 4, 0);
|
||||
}
|
||||
|
||||
vkCmdEndRenderPass(cmdBuffers[i]);
|
||||
|
||||
if (vkDebug::DebugMarker::active)
|
||||
{
|
||||
vkDebug::DebugMarker::endRegion(cmdBuffers[i]);
|
||||
}
|
||||
|
||||
VK_CHECK_RESULT(vkEndCommandBuffer(cmdBuffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// Submit the text command buffers to a queue
|
||||
void submit(VkQueue targetQueue, uint32_t bufferindex, VkSubmitInfo submitInfo)
|
||||
{
|
||||
if (!visible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
submitInfo.pCommandBuffers = &cmdBuffers[bufferindex];
|
||||
submitInfo.commandBufferCount = 1;
|
||||
|
||||
VK_CHECK_RESULT(vkQueueSubmit(targetQueue, 1, &submitInfo, VK_NULL_HANDLE));
|
||||
}
|
||||
|
||||
void reallocateCommandBuffers()
|
||||
{
|
||||
vkFreeCommandBuffers(device, commandPool, static_cast<uint32_t>(cmdBuffers.size()), cmdBuffers.data());
|
||||
|
||||
VkCommandBufferAllocateInfo cmdBufAllocateInfo =
|
||||
vkTools::initializers::commandBufferAllocateInfo(
|
||||
commandPool,
|
||||
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
static_cast<uint32_t>(cmdBuffers.size()));
|
||||
|
||||
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, cmdBuffers.data()));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// vi: set sw=2 ts=4 expandtab:
|
||||
@@ -0,0 +1,918 @@
|
||||
/*
|
||||
* Copyright 2016 Sascha Willems - www.saschawillems.de
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Assorted commonly used Vulkan helper functions
|
||||
*
|
||||
* asserts on unhandled cases in setImageLayout added by Mark Callow, 2017.3.3.
|
||||
*/
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define _CRT_SECURE_NO_WARNINGS // For fopen
|
||||
#endif
|
||||
|
||||
#include "vulkantools.h"
|
||||
#include "unused.h"
|
||||
|
||||
namespace vkTools
|
||||
{
|
||||
|
||||
VkBool32 checkGlobalExtensionPresent(const char* extensionName)
|
||||
{
|
||||
uint32_t extensionCount = 0;
|
||||
std::vector<VkExtensionProperties> extensions;
|
||||
vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL);
|
||||
extensions.resize(extensionCount);
|
||||
vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, extensions.data());
|
||||
for (auto& ext : extensions)
|
||||
{
|
||||
if (!strcmp(extensionName, ext.extensionName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
VkBool32 checkDeviceExtensionPresent(VkPhysicalDevice physicalDevice, const char* extensionName)
|
||||
{
|
||||
uint32_t extensionCount = 0;
|
||||
std::vector<VkExtensionProperties> extensions;
|
||||
vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &extensionCount, NULL);
|
||||
extensions.resize(extensionCount);
|
||||
vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &extensionCount, extensions.data());
|
||||
for (auto& ext : extensions)
|
||||
{
|
||||
if (!strcmp(extensionName, ext.extensionName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string errorString(VkResult errorCode)
|
||||
{
|
||||
switch (errorCode)
|
||||
{
|
||||
#define STR(r) case VK_ ##r: return #r
|
||||
STR(NOT_READY);
|
||||
STR(TIMEOUT);
|
||||
STR(EVENT_SET);
|
||||
STR(EVENT_RESET);
|
||||
STR(INCOMPLETE);
|
||||
STR(ERROR_OUT_OF_HOST_MEMORY);
|
||||
STR(ERROR_OUT_OF_DEVICE_MEMORY);
|
||||
STR(ERROR_INITIALIZATION_FAILED);
|
||||
STR(ERROR_DEVICE_LOST);
|
||||
STR(ERROR_MEMORY_MAP_FAILED);
|
||||
STR(ERROR_LAYER_NOT_PRESENT);
|
||||
STR(ERROR_EXTENSION_NOT_PRESENT);
|
||||
STR(ERROR_FEATURE_NOT_PRESENT);
|
||||
STR(ERROR_INCOMPATIBLE_DRIVER);
|
||||
STR(ERROR_TOO_MANY_OBJECTS);
|
||||
STR(ERROR_FORMAT_NOT_SUPPORTED);
|
||||
STR(ERROR_SURFACE_LOST_KHR);
|
||||
STR(ERROR_NATIVE_WINDOW_IN_USE_KHR);
|
||||
STR(SUBOPTIMAL_KHR);
|
||||
STR(ERROR_OUT_OF_DATE_KHR);
|
||||
STR(ERROR_INCOMPATIBLE_DISPLAY_KHR);
|
||||
STR(ERROR_VALIDATION_FAILED_EXT);
|
||||
STR(ERROR_INVALID_SHADER_NV);
|
||||
#undef STR
|
||||
default:
|
||||
return "UNKNOWN_ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
VkBool32 getSupportedDepthFormat(VkPhysicalDevice physicalDevice, VkFormat *depthFormat)
|
||||
{
|
||||
// Since all depth formats may be optional, we need to find a suitable depth format to use
|
||||
// Start with the highest precision packed format
|
||||
std::vector<VkFormat> depthFormats = {
|
||||
VK_FORMAT_D32_SFLOAT_S8_UINT,
|
||||
VK_FORMAT_D32_SFLOAT,
|
||||
VK_FORMAT_D24_UNORM_S8_UINT,
|
||||
VK_FORMAT_D16_UNORM_S8_UINT,
|
||||
VK_FORMAT_D16_UNORM
|
||||
};
|
||||
|
||||
for (auto& format : depthFormats)
|
||||
{
|
||||
VkFormatProperties formatProps;
|
||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProps);
|
||||
// Format must support depth stencil attachment for optimal tiling
|
||||
if (formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
|
||||
{
|
||||
*depthFormat = format;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create an image memory barrier for changing the layout of
|
||||
// an image and put it into an active command buffer
|
||||
// See chapter 11.4 "Image Layout" for details
|
||||
|
||||
void setImageLayout(
|
||||
VkCommandBuffer cmdbuffer,
|
||||
VkImage image,
|
||||
VkImageAspectFlags /*aspectMask*/,
|
||||
VkImageLayout oldImageLayout,
|
||||
VkImageLayout newImageLayout,
|
||||
VkImageSubresourceRange subresourceRange)
|
||||
{
|
||||
// Create an image barrier object
|
||||
VkImageMemoryBarrier imageMemoryBarrier = vkTools::initializers::imageMemoryBarrier();
|
||||
imageMemoryBarrier.oldLayout = oldImageLayout;
|
||||
imageMemoryBarrier.newLayout = newImageLayout;
|
||||
imageMemoryBarrier.image = image;
|
||||
imageMemoryBarrier.subresourceRange = subresourceRange;
|
||||
|
||||
// Source layouts (old)
|
||||
// Source access mask controls actions that have to be finished on the old layout
|
||||
// before it will be transitioned to the new layout
|
||||
switch (oldImageLayout)
|
||||
{
|
||||
case VK_IMAGE_LAYOUT_UNDEFINED:
|
||||
// Image layout is undefined (or does not matter)
|
||||
// Only valid as initial layout
|
||||
// No flags required, listed only for completeness
|
||||
imageMemoryBarrier.srcAccessMask = 0;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_PREINITIALIZED:
|
||||
// Image is preinitialized
|
||||
// Only valid as initial layout for linear images, preserves memory contents
|
||||
// Make sure host writes have been finished
|
||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
||||
// Image is a color attachment
|
||||
// Make sure any writes to the color buffer have been finished
|
||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
|
||||
// Image is a depth/stencil attachment
|
||||
// Make sure any writes to the depth/stencil buffer have been finished
|
||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
||||
// Image is a transfer source
|
||||
// Make sure any reads from the image have been finished
|
||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
||||
// Image is a transfer destination
|
||||
// Make sure any writes to the image have been finished
|
||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
|
||||
// Image is read by a shader
|
||||
// Make sure any shader reads from the image have been finished
|
||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0); // Attempt to use unhandled case.
|
||||
}
|
||||
|
||||
// Target layouts (new)
|
||||
// Destination access mask controls the dependency for the new image layout
|
||||
switch (newImageLayout)
|
||||
{
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
||||
// Image will be used as a transfer destination
|
||||
// Make sure any writes to the image have been finished
|
||||
imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
||||
// Image will be used as a transfer source
|
||||
// Make sure any reads from and writes to the image have been finished
|
||||
imageMemoryBarrier.srcAccessMask = imageMemoryBarrier.srcAccessMask | VK_ACCESS_TRANSFER_READ_BIT;
|
||||
imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
||||
// Image will be used as a color attachment
|
||||
// Make sure any writes to the color buffer have been finished
|
||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
imageMemoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
|
||||
// Image layout will be used as a depth/stencil attachment
|
||||
// Make sure any writes to depth/stencil buffer have been finished
|
||||
imageMemoryBarrier.dstAccessMask = imageMemoryBarrier.dstAccessMask | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
|
||||
// Image will be read in a shader (sampler, input attachment)
|
||||
// Make sure any writes to the image have been finished
|
||||
if (imageMemoryBarrier.srcAccessMask == 0)
|
||||
{
|
||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
}
|
||||
imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0); // Attempt to use unhandled case.
|
||||
}
|
||||
|
||||
// Put barrier on top
|
||||
VkPipelineStageFlags srcStageFlags = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
|
||||
VkPipelineStageFlags destStageFlags = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
|
||||
|
||||
// Put barrier inside setup command buffer
|
||||
vkCmdPipelineBarrier(
|
||||
cmdbuffer,
|
||||
srcStageFlags,
|
||||
destStageFlags,
|
||||
0,
|
||||
0, nullptr,
|
||||
0, nullptr,
|
||||
1, &imageMemoryBarrier);
|
||||
}
|
||||
|
||||
// Fixed sub resource on first mip level and layer
|
||||
void setImageLayout(
|
||||
VkCommandBuffer cmdbuffer,
|
||||
VkImage image,
|
||||
VkImageAspectFlags aspectMask,
|
||||
VkImageLayout oldImageLayout,
|
||||
VkImageLayout newImageLayout)
|
||||
{
|
||||
VkImageSubresourceRange subresourceRange = {};
|
||||
subresourceRange.aspectMask = aspectMask;
|
||||
subresourceRange.baseMipLevel = 0;
|
||||
subresourceRange.levelCount = 1;
|
||||
subresourceRange.layerCount = 1;
|
||||
setImageLayout(cmdbuffer, image, aspectMask, oldImageLayout, newImageLayout, subresourceRange);
|
||||
}
|
||||
|
||||
void exitFatal(std::string message, std::string caption)
|
||||
{
|
||||
(void)SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, caption.c_str(),
|
||||
message.c_str(), NULL);
|
||||
std::cerr << message << "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string readTextFile(const char *fileName)
|
||||
{
|
||||
std::string fileContent;
|
||||
std::ifstream fileStream(fileName, std::ios::in);
|
||||
if (!fileStream.is_open()) {
|
||||
printf("File %s not found\n", fileName);
|
||||
return "";
|
||||
}
|
||||
std::string line = "";
|
||||
while (!fileStream.eof()) {
|
||||
getline(fileStream, line);
|
||||
fileContent.append(line + "\n");
|
||||
}
|
||||
fileStream.close();
|
||||
return fileContent;
|
||||
}
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
// Android shaders are stored as assets in the apk
|
||||
// So they need to be loaded via the asset manager
|
||||
VkShaderModule loadShader(AAssetManager* assetManager, const char *fileName, VkDevice device, VkShaderStageFlagBits stage)
|
||||
{
|
||||
// Load shader from compressed asset
|
||||
AAsset* asset = AAssetManager_open(assetManager, fileName, AASSET_MODE_STREAMING);
|
||||
assert(asset);
|
||||
size_t size = AAsset_getLength(asset);
|
||||
assert(size > 0);
|
||||
|
||||
char *shaderCode = new char[size];
|
||||
AAsset_read(asset, shaderCode, size);
|
||||
AAsset_close(asset);
|
||||
|
||||
VkShaderModule shaderModule;
|
||||
VkShaderModuleCreateInfo moduleCreateInfo;
|
||||
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
moduleCreateInfo.pNext = NULL;
|
||||
moduleCreateInfo.codeSize = size;
|
||||
moduleCreateInfo.pCode = (uint32_t*)shaderCode;
|
||||
moduleCreateInfo.flags = 0;
|
||||
|
||||
VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule));
|
||||
|
||||
delete[] shaderCode;
|
||||
|
||||
return shaderModule;
|
||||
}
|
||||
#else
|
||||
VkShaderModule loadShader(const char *fileName, VkDevice device, VkShaderStageFlagBits /*stage*/)
|
||||
{
|
||||
size_t size;
|
||||
|
||||
FILE *fp = fopen(fileName, "rb");
|
||||
assert(fp);
|
||||
|
||||
fseek(fp, 0L, SEEK_END);
|
||||
size = ftell(fp);
|
||||
|
||||
fseek(fp, 0L, SEEK_SET);
|
||||
|
||||
//shaderCode = malloc(size);
|
||||
char *shaderCode = new char[size];
|
||||
U_ASSERT_ONLY size_t retval = fread(shaderCode, size, 1, fp);
|
||||
assert(retval == 1);
|
||||
(void)retval; // Supress VC++ unused variable warning for Release config.
|
||||
assert(size > 0);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
VkShaderModule shaderModule;
|
||||
VkShaderModuleCreateInfo moduleCreateInfo;
|
||||
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
moduleCreateInfo.pNext = NULL;
|
||||
moduleCreateInfo.codeSize = size;
|
||||
moduleCreateInfo.pCode = (uint32_t*)shaderCode;
|
||||
moduleCreateInfo.flags = 0;
|
||||
|
||||
VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule));
|
||||
|
||||
delete[] shaderCode;
|
||||
|
||||
return shaderModule;
|
||||
}
|
||||
#endif
|
||||
|
||||
VkShaderModule loadShaderGLSL(const char *fileName, VkDevice device, VkShaderStageFlagBits stage)
|
||||
{
|
||||
std::string shaderSrc = readTextFile(fileName);
|
||||
const char *shaderCode = shaderSrc.c_str();
|
||||
size_t size = strlen(shaderCode);
|
||||
assert(size > 0);
|
||||
|
||||
VkShaderModule shaderModule;
|
||||
VkShaderModuleCreateInfo moduleCreateInfo;
|
||||
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
moduleCreateInfo.pNext = NULL;
|
||||
moduleCreateInfo.codeSize = 3 * sizeof(uint32_t) + size + 1;
|
||||
moduleCreateInfo.pCode = (uint32_t*)malloc(moduleCreateInfo.codeSize);
|
||||
moduleCreateInfo.flags = 0;
|
||||
|
||||
// Magic SPV number
|
||||
((uint32_t *)moduleCreateInfo.pCode)[0] = 0x07230203;
|
||||
((uint32_t *)moduleCreateInfo.pCode)[1] = 0;
|
||||
((uint32_t *)moduleCreateInfo.pCode)[2] = stage;
|
||||
memcpy(((uint32_t *)moduleCreateInfo.pCode + 3), shaderCode, size + 1);
|
||||
|
||||
VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule));
|
||||
|
||||
return shaderModule;
|
||||
}
|
||||
|
||||
VkImageMemoryBarrier prePresentBarrier(VkImage presentImage)
|
||||
{
|
||||
VkImageMemoryBarrier imageMemoryBarrier = vkTools::initializers::imageMemoryBarrier();
|
||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
imageMemoryBarrier.dstAccessMask = 0;
|
||||
imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
|
||||
imageMemoryBarrier.image = presentImage;
|
||||
return imageMemoryBarrier;
|
||||
}
|
||||
|
||||
VkImageMemoryBarrier postPresentBarrier(VkImage presentImage)
|
||||
{
|
||||
VkImageMemoryBarrier imageMemoryBarrier = vkTools::initializers::imageMemoryBarrier();
|
||||
imageMemoryBarrier.srcAccessMask = 0;
|
||||
imageMemoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
|
||||
imageMemoryBarrier.image = presentImage;
|
||||
return imageMemoryBarrier;
|
||||
}
|
||||
|
||||
void destroyUniformData(VkDevice device, vkTools::UniformData *uniformData)
|
||||
{
|
||||
if (uniformData->mapped != nullptr)
|
||||
{
|
||||
vkUnmapMemory(device, uniformData->memory);
|
||||
}
|
||||
vkDestroyBuffer(device, uniformData->buffer, nullptr);
|
||||
vkFreeMemory(device, uniformData->memory, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
VkMemoryAllocateInfo vkTools::initializers::memoryAllocateInfo()
|
||||
{
|
||||
VkMemoryAllocateInfo memAllocInfo = {};
|
||||
memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
memAllocInfo.pNext = NULL;
|
||||
memAllocInfo.allocationSize = 0;
|
||||
memAllocInfo.memoryTypeIndex = 0;
|
||||
return memAllocInfo;
|
||||
}
|
||||
|
||||
VkCommandBufferAllocateInfo vkTools::initializers::commandBufferAllocateInfo(VkCommandPool commandPool, VkCommandBufferLevel level, uint32_t bufferCount)
|
||||
{
|
||||
VkCommandBufferAllocateInfo commandBufferAllocateInfo = {};
|
||||
commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
commandBufferAllocateInfo.commandPool = commandPool;
|
||||
commandBufferAllocateInfo.level = level;
|
||||
commandBufferAllocateInfo.commandBufferCount = bufferCount;
|
||||
return commandBufferAllocateInfo;
|
||||
}
|
||||
|
||||
VkCommandPoolCreateInfo vkTools::initializers::commandPoolCreateInfo()
|
||||
{
|
||||
VkCommandPoolCreateInfo cmdPoolCreateInfo = {};
|
||||
cmdPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
return cmdPoolCreateInfo;
|
||||
}
|
||||
|
||||
VkCommandBufferBeginInfo vkTools::initializers::commandBufferBeginInfo()
|
||||
{
|
||||
VkCommandBufferBeginInfo cmdBufferBeginInfo = {};
|
||||
cmdBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
cmdBufferBeginInfo.pNext = NULL;
|
||||
return cmdBufferBeginInfo;
|
||||
}
|
||||
|
||||
VkCommandBufferInheritanceInfo vkTools::initializers::commandBufferInheritanceInfo()
|
||||
{
|
||||
VkCommandBufferInheritanceInfo cmdBufferInheritanceInfo = {};
|
||||
cmdBufferInheritanceInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
|
||||
return cmdBufferInheritanceInfo;
|
||||
}
|
||||
|
||||
VkRenderPassBeginInfo vkTools::initializers::renderPassBeginInfo()
|
||||
{
|
||||
VkRenderPassBeginInfo renderPassBeginInfo = {};
|
||||
renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
||||
renderPassBeginInfo.pNext = NULL;
|
||||
return renderPassBeginInfo;
|
||||
}
|
||||
|
||||
VkRenderPassCreateInfo vkTools::initializers::renderPassCreateInfo()
|
||||
{
|
||||
VkRenderPassCreateInfo renderPassCreateInfo = {};
|
||||
renderPassCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
renderPassCreateInfo.pNext = NULL;
|
||||
return renderPassCreateInfo;
|
||||
}
|
||||
|
||||
VkImageMemoryBarrier vkTools::initializers::imageMemoryBarrier()
|
||||
{
|
||||
VkImageMemoryBarrier imageMemoryBarrier = {};
|
||||
imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
imageMemoryBarrier.pNext = NULL;
|
||||
// Some default values
|
||||
imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
return imageMemoryBarrier;
|
||||
}
|
||||
|
||||
VkBufferMemoryBarrier vkTools::initializers::bufferMemoryBarrier()
|
||||
{
|
||||
VkBufferMemoryBarrier bufferMemoryBarrier = {};
|
||||
bufferMemoryBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
|
||||
bufferMemoryBarrier.pNext = NULL;
|
||||
return bufferMemoryBarrier;
|
||||
}
|
||||
|
||||
VkMemoryBarrier vkTools::initializers::memoryBarrier()
|
||||
{
|
||||
VkMemoryBarrier memoryBarrier = {};
|
||||
memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
|
||||
memoryBarrier.pNext = NULL;
|
||||
return memoryBarrier;
|
||||
}
|
||||
|
||||
VkImageCreateInfo vkTools::initializers::imageCreateInfo()
|
||||
{
|
||||
VkImageCreateInfo imageCreateInfo = {};
|
||||
imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
imageCreateInfo.pNext = NULL;
|
||||
return imageCreateInfo;
|
||||
}
|
||||
|
||||
VkSamplerCreateInfo vkTools::initializers::samplerCreateInfo()
|
||||
{
|
||||
VkSamplerCreateInfo samplerCreateInfo = {};
|
||||
samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||
samplerCreateInfo.pNext = NULL;
|
||||
return samplerCreateInfo;
|
||||
}
|
||||
|
||||
VkImageViewCreateInfo vkTools::initializers::imageViewCreateInfo()
|
||||
{
|
||||
VkImageViewCreateInfo imageViewCreateInfo = {};
|
||||
imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
imageViewCreateInfo.pNext = NULL;
|
||||
return imageViewCreateInfo;
|
||||
}
|
||||
|
||||
VkFramebufferCreateInfo vkTools::initializers::framebufferCreateInfo()
|
||||
{
|
||||
VkFramebufferCreateInfo framebufferCreateInfo = {};
|
||||
framebufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
framebufferCreateInfo.pNext = NULL;
|
||||
return framebufferCreateInfo;
|
||||
}
|
||||
|
||||
VkSemaphoreCreateInfo vkTools::initializers::semaphoreCreateInfo()
|
||||
{
|
||||
VkSemaphoreCreateInfo semaphoreCreateInfo = {};
|
||||
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||
semaphoreCreateInfo.pNext = NULL;
|
||||
semaphoreCreateInfo.flags = 0;
|
||||
return semaphoreCreateInfo;
|
||||
}
|
||||
|
||||
VkFenceCreateInfo vkTools::initializers::fenceCreateInfo(VkFenceCreateFlags flags)
|
||||
{
|
||||
VkFenceCreateInfo fenceCreateInfo = {};
|
||||
fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
fenceCreateInfo.flags = flags;
|
||||
return fenceCreateInfo;
|
||||
}
|
||||
|
||||
VkEventCreateInfo vkTools::initializers::eventCreateInfo()
|
||||
{
|
||||
VkEventCreateInfo eventCreateInfo = {};
|
||||
eventCreateInfo.sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO;
|
||||
return eventCreateInfo;
|
||||
}
|
||||
|
||||
VkSubmitInfo vkTools::initializers::submitInfo()
|
||||
{
|
||||
VkSubmitInfo submitInfo = {};
|
||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submitInfo.pNext = NULL;
|
||||
return submitInfo;
|
||||
}
|
||||
|
||||
VkViewport vkTools::initializers::viewport(
|
||||
float width,
|
||||
float height,
|
||||
float minDepth,
|
||||
float maxDepth)
|
||||
{
|
||||
VkViewport viewport = {};
|
||||
viewport.width = width;
|
||||
viewport.height = height;
|
||||
viewport.minDepth = minDepth;
|
||||
viewport.maxDepth = maxDepth;
|
||||
return viewport;
|
||||
}
|
||||
|
||||
VkRect2D vkTools::initializers::rect2D(
|
||||
int32_t width,
|
||||
int32_t height,
|
||||
int32_t offsetX,
|
||||
int32_t offsetY)
|
||||
{
|
||||
VkRect2D rect2D = {};
|
||||
rect2D.extent.width = width;
|
||||
rect2D.extent.height = height;
|
||||
rect2D.offset.x = offsetX;
|
||||
rect2D.offset.y = offsetY;
|
||||
return rect2D;
|
||||
}
|
||||
|
||||
VkBufferCreateInfo vkTools::initializers::bufferCreateInfo()
|
||||
{
|
||||
VkBufferCreateInfo bufCreateInfo = {};
|
||||
bufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
return bufCreateInfo;
|
||||
}
|
||||
|
||||
VkBufferCreateInfo vkTools::initializers::bufferCreateInfo(
|
||||
VkBufferUsageFlags usage,
|
||||
VkDeviceSize size)
|
||||
{
|
||||
VkBufferCreateInfo bufCreateInfo = {};
|
||||
bufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
bufCreateInfo.pNext = NULL;
|
||||
bufCreateInfo.usage = usage;
|
||||
bufCreateInfo.size = size;
|
||||
bufCreateInfo.flags = 0;
|
||||
return bufCreateInfo;
|
||||
}
|
||||
|
||||
VkDescriptorPoolCreateInfo vkTools::initializers::descriptorPoolCreateInfo(
|
||||
uint32_t poolSizeCount,
|
||||
VkDescriptorPoolSize* pPoolSizes,
|
||||
uint32_t maxSets)
|
||||
{
|
||||
VkDescriptorPoolCreateInfo descriptorPoolInfo = {};
|
||||
descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||
descriptorPoolInfo.pNext = NULL;
|
||||
descriptorPoolInfo.poolSizeCount = poolSizeCount;
|
||||
descriptorPoolInfo.pPoolSizes = pPoolSizes;
|
||||
descriptorPoolInfo.maxSets = maxSets;
|
||||
return descriptorPoolInfo;
|
||||
}
|
||||
|
||||
VkDescriptorPoolSize vkTools::initializers::descriptorPoolSize(
|
||||
VkDescriptorType type,
|
||||
uint32_t descriptorCount)
|
||||
{
|
||||
VkDescriptorPoolSize descriptorPoolSize = {};
|
||||
descriptorPoolSize.type = type;
|
||||
descriptorPoolSize.descriptorCount = descriptorCount;
|
||||
return descriptorPoolSize;
|
||||
}
|
||||
|
||||
VkDescriptorSetLayoutBinding vkTools::initializers::descriptorSetLayoutBinding(
|
||||
VkDescriptorType type,
|
||||
VkShaderStageFlags stageFlags,
|
||||
uint32_t binding)
|
||||
{
|
||||
VkDescriptorSetLayoutBinding setLayoutBinding = {};
|
||||
setLayoutBinding.descriptorType = type;
|
||||
setLayoutBinding.stageFlags = stageFlags;
|
||||
setLayoutBinding.binding = binding;
|
||||
// Default value in all examples
|
||||
setLayoutBinding.descriptorCount = 1;
|
||||
return setLayoutBinding;
|
||||
}
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo vkTools::initializers::descriptorSetLayoutCreateInfo(
|
||||
const VkDescriptorSetLayoutBinding* pBindings,
|
||||
uint32_t bindingCount)
|
||||
{
|
||||
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = {};
|
||||
descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
||||
descriptorSetLayoutCreateInfo.pNext = NULL;
|
||||
descriptorSetLayoutCreateInfo.pBindings = pBindings;
|
||||
descriptorSetLayoutCreateInfo.bindingCount = bindingCount;
|
||||
return descriptorSetLayoutCreateInfo;
|
||||
}
|
||||
|
||||
VkPipelineLayoutCreateInfo vkTools::initializers::pipelineLayoutCreateInfo(
|
||||
const VkDescriptorSetLayout* pSetLayouts,
|
||||
uint32_t setLayoutCount)
|
||||
{
|
||||
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {};
|
||||
pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||
pipelineLayoutCreateInfo.pNext = NULL;
|
||||
pipelineLayoutCreateInfo.setLayoutCount = setLayoutCount;
|
||||
pipelineLayoutCreateInfo.pSetLayouts = pSetLayouts;
|
||||
return pipelineLayoutCreateInfo;
|
||||
}
|
||||
|
||||
VkDescriptorSetAllocateInfo vkTools::initializers::descriptorSetAllocateInfo(
|
||||
VkDescriptorPool descriptorPool,
|
||||
const VkDescriptorSetLayout* pSetLayouts,
|
||||
uint32_t descriptorSetCount)
|
||||
{
|
||||
VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = {};
|
||||
descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||
descriptorSetAllocateInfo.pNext = NULL;
|
||||
descriptorSetAllocateInfo.descriptorPool = descriptorPool;
|
||||
descriptorSetAllocateInfo.pSetLayouts = pSetLayouts;
|
||||
descriptorSetAllocateInfo.descriptorSetCount = descriptorSetCount;
|
||||
return descriptorSetAllocateInfo;
|
||||
}
|
||||
|
||||
VkDescriptorImageInfo vkTools::initializers::descriptorImageInfo(VkSampler sampler, VkImageView imageView, VkImageLayout imageLayout)
|
||||
{
|
||||
VkDescriptorImageInfo descriptorImageInfo = {};
|
||||
descriptorImageInfo.sampler = sampler;
|
||||
descriptorImageInfo.imageView = imageView;
|
||||
descriptorImageInfo.imageLayout = imageLayout;
|
||||
return descriptorImageInfo;
|
||||
}
|
||||
|
||||
VkWriteDescriptorSet vkTools::initializers::writeDescriptorSet(
|
||||
VkDescriptorSet dstSet,
|
||||
VkDescriptorType type,
|
||||
uint32_t binding,
|
||||
VkDescriptorBufferInfo* bufferInfo)
|
||||
{
|
||||
VkWriteDescriptorSet writeDescriptorSet = {};
|
||||
writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writeDescriptorSet.pNext = NULL;
|
||||
writeDescriptorSet.dstSet = dstSet;
|
||||
writeDescriptorSet.descriptorType = type;
|
||||
writeDescriptorSet.dstBinding = binding;
|
||||
writeDescriptorSet.pBufferInfo = bufferInfo;
|
||||
// Default value in all examples
|
||||
writeDescriptorSet.descriptorCount = 1;
|
||||
return writeDescriptorSet;
|
||||
}
|
||||
|
||||
VkWriteDescriptorSet vkTools::initializers::writeDescriptorSet(
|
||||
VkDescriptorSet dstSet,
|
||||
VkDescriptorType type,
|
||||
uint32_t binding,
|
||||
VkDescriptorImageInfo * imageInfo)
|
||||
{
|
||||
VkWriteDescriptorSet writeDescriptorSet = {};
|
||||
writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writeDescriptorSet.pNext = NULL;
|
||||
writeDescriptorSet.dstSet = dstSet;
|
||||
writeDescriptorSet.descriptorType = type;
|
||||
writeDescriptorSet.dstBinding = binding;
|
||||
writeDescriptorSet.pImageInfo = imageInfo;
|
||||
// Default value in all examples
|
||||
writeDescriptorSet.descriptorCount = 1;
|
||||
return writeDescriptorSet;
|
||||
}
|
||||
|
||||
VkVertexInputBindingDescription vkTools::initializers::vertexInputBindingDescription(
|
||||
uint32_t binding,
|
||||
uint32_t stride,
|
||||
VkVertexInputRate inputRate)
|
||||
{
|
||||
VkVertexInputBindingDescription vInputBindDescription = {};
|
||||
vInputBindDescription.binding = binding;
|
||||
vInputBindDescription.stride = stride;
|
||||
vInputBindDescription.inputRate = inputRate;
|
||||
return vInputBindDescription;
|
||||
}
|
||||
|
||||
VkVertexInputAttributeDescription vkTools::initializers::vertexInputAttributeDescription(
|
||||
uint32_t binding,
|
||||
uint32_t location,
|
||||
VkFormat format,
|
||||
uint32_t offset)
|
||||
{
|
||||
VkVertexInputAttributeDescription vInputAttribDescription = {};
|
||||
vInputAttribDescription.location = location;
|
||||
vInputAttribDescription.binding = binding;
|
||||
vInputAttribDescription.format = format;
|
||||
vInputAttribDescription.offset = offset;
|
||||
return vInputAttribDescription;
|
||||
}
|
||||
|
||||
VkPipelineVertexInputStateCreateInfo vkTools::initializers::pipelineVertexInputStateCreateInfo()
|
||||
{
|
||||
VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo = {};
|
||||
pipelineVertexInputStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
||||
pipelineVertexInputStateCreateInfo.pNext = NULL;
|
||||
return pipelineVertexInputStateCreateInfo;
|
||||
}
|
||||
|
||||
VkPipelineInputAssemblyStateCreateInfo vkTools::initializers::pipelineInputAssemblyStateCreateInfo(
|
||||
VkPrimitiveTopology topology,
|
||||
VkPipelineInputAssemblyStateCreateFlags flags,
|
||||
VkBool32 primitiveRestartEnable)
|
||||
{
|
||||
VkPipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo = {};
|
||||
pipelineInputAssemblyStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
||||
pipelineInputAssemblyStateCreateInfo.topology = topology;
|
||||
pipelineInputAssemblyStateCreateInfo.flags = flags;
|
||||
pipelineInputAssemblyStateCreateInfo.primitiveRestartEnable = primitiveRestartEnable;
|
||||
return pipelineInputAssemblyStateCreateInfo;
|
||||
}
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo vkTools::initializers::pipelineRasterizationStateCreateInfo(
|
||||
VkPolygonMode polygonMode,
|
||||
VkCullModeFlags cullMode,
|
||||
VkFrontFace frontFace,
|
||||
VkPipelineRasterizationStateCreateFlags flags)
|
||||
{
|
||||
VkPipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo = {};
|
||||
pipelineRasterizationStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
|
||||
pipelineRasterizationStateCreateInfo.polygonMode = polygonMode;
|
||||
pipelineRasterizationStateCreateInfo.cullMode = cullMode;
|
||||
pipelineRasterizationStateCreateInfo.frontFace = frontFace;
|
||||
pipelineRasterizationStateCreateInfo.flags = flags;
|
||||
pipelineRasterizationStateCreateInfo.depthClampEnable = VK_TRUE;
|
||||
pipelineRasterizationStateCreateInfo.lineWidth = 1.0f;
|
||||
return pipelineRasterizationStateCreateInfo;
|
||||
}
|
||||
|
||||
VkPipelineColorBlendAttachmentState vkTools::initializers::pipelineColorBlendAttachmentState(
|
||||
VkColorComponentFlags colorWriteMask,
|
||||
VkBool32 blendEnable)
|
||||
{
|
||||
VkPipelineColorBlendAttachmentState pipelineColorBlendAttachmentState = {};
|
||||
pipelineColorBlendAttachmentState.colorWriteMask = colorWriteMask;
|
||||
pipelineColorBlendAttachmentState.blendEnable = blendEnable;
|
||||
return pipelineColorBlendAttachmentState;
|
||||
}
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo vkTools::initializers::pipelineColorBlendStateCreateInfo(
|
||||
uint32_t attachmentCount,
|
||||
const VkPipelineColorBlendAttachmentState * pAttachments)
|
||||
{
|
||||
VkPipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo = {};
|
||||
pipelineColorBlendStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
||||
pipelineColorBlendStateCreateInfo.pNext = NULL;
|
||||
pipelineColorBlendStateCreateInfo.attachmentCount = attachmentCount;
|
||||
pipelineColorBlendStateCreateInfo.pAttachments = pAttachments;
|
||||
return pipelineColorBlendStateCreateInfo;
|
||||
}
|
||||
|
||||
VkPipelineDepthStencilStateCreateInfo vkTools::initializers::pipelineDepthStencilStateCreateInfo(
|
||||
VkBool32 depthTestEnable,
|
||||
VkBool32 depthWriteEnable,
|
||||
VkCompareOp depthCompareOp)
|
||||
{
|
||||
VkPipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo = {};
|
||||
pipelineDepthStencilStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
|
||||
pipelineDepthStencilStateCreateInfo.depthTestEnable = depthTestEnable;
|
||||
pipelineDepthStencilStateCreateInfo.depthWriteEnable = depthWriteEnable;
|
||||
pipelineDepthStencilStateCreateInfo.depthCompareOp = depthCompareOp;
|
||||
pipelineDepthStencilStateCreateInfo.front = pipelineDepthStencilStateCreateInfo.back;
|
||||
pipelineDepthStencilStateCreateInfo.back.compareOp = VK_COMPARE_OP_ALWAYS;
|
||||
return pipelineDepthStencilStateCreateInfo;
|
||||
}
|
||||
|
||||
VkPipelineViewportStateCreateInfo vkTools::initializers::pipelineViewportStateCreateInfo(
|
||||
uint32_t viewportCount,
|
||||
uint32_t scissorCount,
|
||||
VkPipelineViewportStateCreateFlags flags)
|
||||
{
|
||||
VkPipelineViewportStateCreateInfo pipelineViewportStateCreateInfo = {};
|
||||
pipelineViewportStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
||||
pipelineViewportStateCreateInfo.viewportCount = viewportCount;
|
||||
pipelineViewportStateCreateInfo.scissorCount = scissorCount;
|
||||
pipelineViewportStateCreateInfo.flags = flags;
|
||||
return pipelineViewportStateCreateInfo;
|
||||
}
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo vkTools::initializers::pipelineMultisampleStateCreateInfo(
|
||||
VkSampleCountFlagBits rasterizationSamples,
|
||||
VkPipelineMultisampleStateCreateFlags flags)
|
||||
{
|
||||
VkPipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo = {};
|
||||
pipelineMultisampleStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
||||
pipelineMultisampleStateCreateInfo.rasterizationSamples = rasterizationSamples;
|
||||
pipelineMultisampleStateCreateInfo.flags = flags;
|
||||
return pipelineMultisampleStateCreateInfo;
|
||||
}
|
||||
|
||||
VkPipelineDynamicStateCreateInfo vkTools::initializers::pipelineDynamicStateCreateInfo(
|
||||
const VkDynamicState * pDynamicStates,
|
||||
uint32_t dynamicStateCount,
|
||||
VkPipelineDynamicStateCreateFlags flags)
|
||||
{
|
||||
VkPipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo = {};
|
||||
pipelineDynamicStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
||||
pipelineDynamicStateCreateInfo.pDynamicStates = pDynamicStates;
|
||||
pipelineDynamicStateCreateInfo.dynamicStateCount = dynamicStateCount;
|
||||
pipelineDynamicStateCreateInfo.flags = flags;
|
||||
return pipelineDynamicStateCreateInfo;
|
||||
}
|
||||
|
||||
VkPipelineTessellationStateCreateInfo vkTools::initializers::pipelineTessellationStateCreateInfo(uint32_t patchControlPoints)
|
||||
{
|
||||
VkPipelineTessellationStateCreateInfo pipelineTessellationStateCreateInfo = {};
|
||||
pipelineTessellationStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
|
||||
pipelineTessellationStateCreateInfo.patchControlPoints = patchControlPoints;
|
||||
return pipelineTessellationStateCreateInfo;
|
||||
}
|
||||
|
||||
VkGraphicsPipelineCreateInfo vkTools::initializers::pipelineCreateInfo(
|
||||
VkPipelineLayout layout,
|
||||
VkRenderPass renderPass,
|
||||
VkPipelineCreateFlags flags)
|
||||
{
|
||||
VkGraphicsPipelineCreateInfo pipelineCreateInfo = {};
|
||||
pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
||||
pipelineCreateInfo.pNext = NULL;
|
||||
pipelineCreateInfo.layout = layout;
|
||||
pipelineCreateInfo.renderPass = renderPass;
|
||||
pipelineCreateInfo.flags = flags;
|
||||
return pipelineCreateInfo;
|
||||
}
|
||||
|
||||
VkComputePipelineCreateInfo vkTools::initializers::computePipelineCreateInfo(VkPipelineLayout layout, VkPipelineCreateFlags flags)
|
||||
{
|
||||
VkComputePipelineCreateInfo computePipelineCreateInfo = {};
|
||||
computePipelineCreateInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
|
||||
computePipelineCreateInfo.layout = layout;
|
||||
computePipelineCreateInfo.flags = flags;
|
||||
return computePipelineCreateInfo;
|
||||
}
|
||||
|
||||
VkPushConstantRange vkTools::initializers::pushConstantRange(
|
||||
VkShaderStageFlags stageFlags,
|
||||
uint32_t size,
|
||||
uint32_t offset)
|
||||
{
|
||||
VkPushConstantRange pushConstantRange = {};
|
||||
pushConstantRange.stageFlags = stageFlags;
|
||||
pushConstantRange.offset = offset;
|
||||
pushConstantRange.size = size;
|
||||
return pushConstantRange;
|
||||
}
|
||||
|
||||
// vi: set sw=2 ts=4 expandtab:
|
||||
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* Copyright 2016 Sascha Willems - www.saschawillems.de
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Assorted commonly used Vulkan helper functions
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "vulkan/vulkan.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#elif defined(__ANDROID__)
|
||||
#include "vulkanandroid.h"
|
||||
#include <android/asset_manager.h>
|
||||
#endif
|
||||
|
||||
#include <SDL3/SDL_messagebox.h>
|
||||
|
||||
// Custom define for better code readability
|
||||
#define VK_FLAGS_NONE 0
|
||||
// Default fence timeout in nanoseconds
|
||||
#define DEFAULT_FENCE_TIMEOUT 100000000000
|
||||
|
||||
#if 0
|
||||
// Macro to check and display Vulkan return results
|
||||
#define VK_CHECK_RESULT(f) \
|
||||
{ \
|
||||
VkResult res = (f); \
|
||||
if (res != VK_SUCCESS) \
|
||||
{ \
|
||||
std::cout << "Fatal : VkResult is \"" << vkTools::errorString(res) << "\" in " << __FILE__ << " at line " << __LINE__ << std::endl; \
|
||||
assert(res == VK_SUCCESS); \
|
||||
} \
|
||||
}
|
||||
#endif
|
||||
#if defined(DEBUG)
|
||||
#include <sstream>
|
||||
extern const char* appName();
|
||||
#define VK_CHECK_RESULT(f) \
|
||||
{ \
|
||||
VkResult res = (f); \
|
||||
if (res != VK_SUCCESS) \
|
||||
{ \
|
||||
std::stringstream msg; \
|
||||
msg << "Fatal error. VkResult is \"" \
|
||||
<< vkTools::errorString(res) << "\" in " << __FILE__ \
|
||||
<< " at line " << __LINE__ << std::endl; \
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, \
|
||||
appName(), \
|
||||
msg.str().c_str(), \
|
||||
NULL); \
|
||||
assert(res == VK_SUCCESS); \
|
||||
} \
|
||||
}
|
||||
#else
|
||||
#define VK_CHECK_RESULT(f) (void)f
|
||||
#endif
|
||||
|
||||
|
||||
namespace vkTools
|
||||
{
|
||||
// Check if extension is globally available
|
||||
VkBool32 checkGlobalExtensionPresent(const char* extensionName);
|
||||
// Check if extension is present on the given device
|
||||
VkBool32 checkDeviceExtensionPresent(VkPhysicalDevice physicalDevice, const char* extensionName);
|
||||
// Return string representation of a vulkan error string
|
||||
std::string errorString(VkResult errorCode);
|
||||
|
||||
// Selected a suitable supported depth format starting with 32 bit down to 16 bit
|
||||
// Returns false if none of the depth formats in the list is supported by the device
|
||||
VkBool32 getSupportedDepthFormat(VkPhysicalDevice physicalDevice, VkFormat *depthFormat);
|
||||
|
||||
// Put an image memory barrier for setting an image layout on the sub resource into the given command buffer
|
||||
void setImageLayout(
|
||||
VkCommandBuffer cmdbuffer,
|
||||
VkImage image,
|
||||
VkImageAspectFlags aspectMask,
|
||||
VkImageLayout oldImageLayout,
|
||||
VkImageLayout newImageLayout,
|
||||
VkImageSubresourceRange subresourceRange);
|
||||
// Uses a fixed sub resource layout with first mip level and layer
|
||||
void setImageLayout(
|
||||
VkCommandBuffer cmdbuffer,
|
||||
VkImage image,
|
||||
VkImageAspectFlags aspectMask,
|
||||
VkImageLayout oldImageLayout,
|
||||
VkImageLayout newImageLayout);
|
||||
|
||||
// Display error message and exit on fatal error
|
||||
void exitFatal(std::string message, std::string caption);
|
||||
// Load a text file (e.g. GLGL shader) into a std::string
|
||||
std::string readTextFile(const char *fileName);
|
||||
// Load a binary file into a buffer (e.g. SPIR-V)
|
||||
char *readBinaryFile(const char *filename, size_t *psize);
|
||||
|
||||
// Load a SPIR-V shader
|
||||
#if defined(__ANDROID__)
|
||||
VkShaderModule loadShader(AAssetManager* assetManager, const char *fileName, VkDevice device, VkShaderStageFlagBits stage);
|
||||
#else
|
||||
VkShaderModule loadShader(const char *fileName, VkDevice device, VkShaderStageFlagBits stage);
|
||||
#endif
|
||||
|
||||
// Load a GLSL shader
|
||||
// Note : Only for testing purposes, support for directly feeding GLSL shaders into Vulkan
|
||||
// may be dropped at some point
|
||||
VkShaderModule loadShaderGLSL(const char *fileName, VkDevice device, VkShaderStageFlagBits stage);
|
||||
|
||||
// Returns a pre-present image memory barrier
|
||||
// Transforms the image's layout from color attachment to present khr
|
||||
VkImageMemoryBarrier prePresentBarrier(VkImage presentImage);
|
||||
|
||||
// Returns a post-present image memory barrier
|
||||
// Transforms the image's layout back from present khr to color attachment
|
||||
VkImageMemoryBarrier postPresentBarrier(VkImage presentImage);
|
||||
|
||||
// Contains all vulkan objects
|
||||
// required for a uniform data object
|
||||
struct UniformData
|
||||
{
|
||||
VkBuffer buffer;
|
||||
VkDeviceMemory memory;
|
||||
VkDescriptorBufferInfo descriptor;
|
||||
uint32_t allocSize;
|
||||
void* mapped = nullptr;
|
||||
};
|
||||
|
||||
// Destroy (and free) Vulkan resources used by a uniform data structure
|
||||
void destroyUniformData(VkDevice device, vkTools::UniformData *uniformData);
|
||||
|
||||
// Contains often used vulkan object initializers
|
||||
// Save lot of VK_STRUCTURE_TYPE assignments
|
||||
// Some initializers are parameterized for convenience
|
||||
namespace initializers
|
||||
{
|
||||
VkMemoryAllocateInfo memoryAllocateInfo();
|
||||
|
||||
VkCommandBufferAllocateInfo commandBufferAllocateInfo(
|
||||
VkCommandPool commandPool,
|
||||
VkCommandBufferLevel level,
|
||||
uint32_t bufferCount);
|
||||
|
||||
VkCommandPoolCreateInfo commandPoolCreateInfo();
|
||||
VkCommandBufferBeginInfo commandBufferBeginInfo();
|
||||
VkCommandBufferInheritanceInfo commandBufferInheritanceInfo();
|
||||
|
||||
VkRenderPassBeginInfo renderPassBeginInfo();
|
||||
VkRenderPassCreateInfo renderPassCreateInfo();
|
||||
|
||||
VkImageMemoryBarrier imageMemoryBarrier();
|
||||
VkBufferMemoryBarrier bufferMemoryBarrier();
|
||||
VkMemoryBarrier memoryBarrier();
|
||||
|
||||
VkImageCreateInfo imageCreateInfo();
|
||||
VkSamplerCreateInfo samplerCreateInfo();
|
||||
VkImageViewCreateInfo imageViewCreateInfo();
|
||||
|
||||
VkFramebufferCreateInfo framebufferCreateInfo();
|
||||
|
||||
VkSemaphoreCreateInfo semaphoreCreateInfo();
|
||||
VkFenceCreateInfo fenceCreateInfo(VkFenceCreateFlags flags);
|
||||
VkEventCreateInfo eventCreateInfo();
|
||||
|
||||
VkSubmitInfo submitInfo();
|
||||
|
||||
VkViewport viewport(
|
||||
float width,
|
||||
float height,
|
||||
float minDepth,
|
||||
float maxDepth);
|
||||
|
||||
VkRect2D rect2D(
|
||||
int32_t width,
|
||||
int32_t height,
|
||||
int32_t offsetX,
|
||||
int32_t offsetY);
|
||||
|
||||
VkBufferCreateInfo bufferCreateInfo();
|
||||
|
||||
VkBufferCreateInfo bufferCreateInfo(
|
||||
VkBufferUsageFlags usage,
|
||||
VkDeviceSize size);
|
||||
|
||||
VkDescriptorPoolCreateInfo descriptorPoolCreateInfo(
|
||||
uint32_t poolSizeCount,
|
||||
VkDescriptorPoolSize* pPoolSizes,
|
||||
uint32_t maxSets);
|
||||
|
||||
VkDescriptorPoolSize descriptorPoolSize(
|
||||
VkDescriptorType type,
|
||||
uint32_t descriptorCount);
|
||||
|
||||
VkDescriptorSetLayoutBinding descriptorSetLayoutBinding(
|
||||
VkDescriptorType type,
|
||||
VkShaderStageFlags stageFlags,
|
||||
uint32_t binding);
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo(
|
||||
const VkDescriptorSetLayoutBinding* pBindings,
|
||||
uint32_t bindingCount);
|
||||
|
||||
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo(
|
||||
const VkDescriptorSetLayout* pSetLayouts,
|
||||
uint32_t setLayoutCount );
|
||||
|
||||
VkDescriptorSetAllocateInfo descriptorSetAllocateInfo(
|
||||
VkDescriptorPool descriptorPool,
|
||||
const VkDescriptorSetLayout* pSetLayouts,
|
||||
uint32_t descriptorSetCount);
|
||||
|
||||
VkDescriptorImageInfo descriptorImageInfo(
|
||||
VkSampler sampler,
|
||||
VkImageView imageView,
|
||||
VkImageLayout imageLayout);
|
||||
|
||||
VkWriteDescriptorSet writeDescriptorSet(
|
||||
VkDescriptorSet dstSet,
|
||||
VkDescriptorType type,
|
||||
uint32_t binding,
|
||||
VkDescriptorBufferInfo* bufferInfo);
|
||||
|
||||
VkWriteDescriptorSet writeDescriptorSet(
|
||||
VkDescriptorSet dstSet,
|
||||
VkDescriptorType type,
|
||||
uint32_t binding,
|
||||
VkDescriptorImageInfo* imageInfo);
|
||||
|
||||
VkVertexInputBindingDescription vertexInputBindingDescription(
|
||||
uint32_t binding,
|
||||
uint32_t stride,
|
||||
VkVertexInputRate inputRate);
|
||||
|
||||
VkVertexInputAttributeDescription vertexInputAttributeDescription(
|
||||
uint32_t binding,
|
||||
uint32_t location,
|
||||
VkFormat format,
|
||||
uint32_t offset);
|
||||
|
||||
VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo();
|
||||
|
||||
VkPipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo(
|
||||
VkPrimitiveTopology topology,
|
||||
VkPipelineInputAssemblyStateCreateFlags flags,
|
||||
VkBool32 primitiveRestartEnable);
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo(
|
||||
VkPolygonMode polygonMode,
|
||||
VkCullModeFlags cullMode,
|
||||
VkFrontFace frontFace,
|
||||
VkPipelineRasterizationStateCreateFlags flags);
|
||||
|
||||
VkPipelineColorBlendAttachmentState pipelineColorBlendAttachmentState(
|
||||
VkColorComponentFlags colorWriteMask,
|
||||
VkBool32 blendEnable);
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo(
|
||||
uint32_t attachmentCount,
|
||||
const VkPipelineColorBlendAttachmentState* pAttachments);
|
||||
|
||||
VkPipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo(
|
||||
VkBool32 depthTestEnable,
|
||||
VkBool32 depthWriteEnable,
|
||||
VkCompareOp depthCompareOp);
|
||||
|
||||
VkPipelineViewportStateCreateInfo pipelineViewportStateCreateInfo(
|
||||
uint32_t viewportCount,
|
||||
uint32_t scissorCount,
|
||||
VkPipelineViewportStateCreateFlags flags);
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo(
|
||||
VkSampleCountFlagBits rasterizationSamples,
|
||||
VkPipelineMultisampleStateCreateFlags flags);
|
||||
|
||||
VkPipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo(
|
||||
const VkDynamicState *pDynamicStates,
|
||||
uint32_t dynamicStateCount,
|
||||
VkPipelineDynamicStateCreateFlags flags);
|
||||
|
||||
VkPipelineTessellationStateCreateInfo pipelineTessellationStateCreateInfo(
|
||||
uint32_t patchControlPoints);
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipelineCreateInfo(
|
||||
VkPipelineLayout layout,
|
||||
VkRenderPass renderPass,
|
||||
VkPipelineCreateFlags flags);
|
||||
|
||||
VkComputePipelineCreateInfo computePipelineCreateInfo(
|
||||
VkPipelineLayout layout,
|
||||
VkPipelineCreateFlags flags);
|
||||
|
||||
VkPushConstantRange pushConstantRange(
|
||||
VkShaderStageFlags stageFlags,
|
||||
uint32_t size,
|
||||
uint32_t offset);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vi: set sw=2 ts=4 expandtab:
|
||||
@@ -0,0 +1,96 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/* $Id: f63e0a9e6eed51ed84a8eea1eff0708c8a6af22b $ */
|
||||
|
||||
/*
|
||||
* Copyright 2015-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @file
|
||||
* @~English
|
||||
*
|
||||
* @brief main() function for SDL app framework.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdio.h>
|
||||
#include "AppBaseSDL.h"
|
||||
#include <SDL3/SDL_main.h>
|
||||
#include "platform_utils.h"
|
||||
#if defined(EMSCRIPTEN)
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
#define SDL_GESTURE_IMPLEMENTATION 1
|
||||
#include "SDL_gesture.h"
|
||||
|
||||
#if defined(SDL_PLATFORM_IOS)
|
||||
#define NEED_MAIN_LOOP 0
|
||||
//int SDL_iPhoneSetAnimationCallback(
|
||||
// SDL_Window * window, int interval,
|
||||
// void (*callback)(void*), void *callbackParam
|
||||
// );
|
||||
#define setAnimationCallback(win, cb, userdata) \
|
||||
SDL_SetiOSAnimationCallback(win, 1, cb, userdata)
|
||||
#elif defined(EMSCRIPTEN)
|
||||
#define NEED_MAIN_LOOP 0
|
||||
//void emscripten_set_main_loop_arg(em_arg_callback_func func, void *arg,
|
||||
// int fps, int simulate_infinite_loop);
|
||||
#define setAnimationCallback(win, cb, userdata) \
|
||||
emscripten_set_main_loop_arg(cb, userdata, 0, 0)
|
||||
#else
|
||||
#define NEED_MAIN_LOOP 1
|
||||
#define setAnimationCallback(win, cb, userdata)
|
||||
#endif
|
||||
|
||||
static void
|
||||
quit() {
|
||||
Gesture_Quit();
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char* argv[])
|
||||
{
|
||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
|
||||
fprintf(stderr, "%s: SDL video initialization failed: %s\n",
|
||||
theApp->name(), SDL_GetError());
|
||||
return 1;
|
||||
}
|
||||
Gesture_Init();
|
||||
atexit(quit);
|
||||
|
||||
InitUTF8CLI(argc, argv);
|
||||
AppBaseSDL::Args args(argv, argv+argc);
|
||||
|
||||
if (!theApp->initialize(args))
|
||||
return 1;
|
||||
|
||||
// Catches events before they are added to the event queue.
|
||||
// May need this for some events that need rapid response...
|
||||
// SDL_SetEventFilter(theApp->onEvent, theApp);
|
||||
// Triggered when event added to queue.
|
||||
SDL_AddEventWatch(theApp->onEvent, theApp);
|
||||
if (!NEED_MAIN_LOOP) {
|
||||
// TODO: Fix this main to work for multiple windows. One way is to have the
|
||||
// application call setAnimationCallback and keep a list of the windows in
|
||||
// this file, calling drawFrame for each window.
|
||||
setAnimationCallback(theApp->getMainWindow(), theApp->onDrawFrame, theApp);
|
||||
// iOS version of SDL will not exit when main completes.
|
||||
// The Emscripten version of the app must be compiled with
|
||||
// -s NO_EXIT_RUNTIME=1 to prevent Emscripten exiting when main completes.
|
||||
return 0;
|
||||
} else {
|
||||
for (;;) {
|
||||
SDL_PumpEvents();
|
||||
theApp->drawFrame();
|
||||
// XXX Let app return a sleeptime from drawFrame()? If so
|
||||
// sleep(sleeptime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2017-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @class LoadTestSample
|
||||
* @~English
|
||||
*
|
||||
* @brief Definition of a base class for texture loading test samples.
|
||||
*
|
||||
* @author Mark Callow, github.com/MarkCallow.
|
||||
*/
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define _USE_MATH_DEFINES
|
||||
#endif
|
||||
#include "LoadTestSample.h"
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
#include <SDL3/SDL_log.h>
|
||||
|
||||
#if !defined(LOADTESTSAMPLE_LOG_GESTURE_DETECTION)
|
||||
// Log detected and completed gestures.
|
||||
#define LOADTESTSAMPLE_LOG_GESTURE_DETECTION 0
|
||||
#endif
|
||||
#if !defined(LOADTESTSAMPLE_LOG_GESTURE_EVENTS)
|
||||
// Log events contributing to gesture detection and gestures.
|
||||
#define LOADTESTSAMPLE_LOG_GESTURE_EVENTS 0
|
||||
#endif
|
||||
#if !defined(LOADTESTSAMPLE_LOG_MOUSE_UP_DOWN_EVENTS)
|
||||
#define LOADTESTSAMPLE_LOG_MOUSE_UP_DOWN_EVENTS 0
|
||||
#endif
|
||||
#if !defined(LOADTESTSAMPLE_LOG_MOUSE_MOTION_EVENTS)
|
||||
#define LOADTESTSAMPLE_LOG_MOUSE_MOTION_EVENTS 0
|
||||
#endif
|
||||
|
||||
#if LOADTESTSAMPLE_LOG_GESTURE_EVENTS
|
||||
#include <sstream>
|
||||
|
||||
const std::string printFingerIds(SDL_Finger* fingers[], uint32_t numFingers) {
|
||||
std::stringstream msg;
|
||||
assert(numFingers > 0);
|
||||
msg << std::hex << std::showbase;
|
||||
msg << "finger id" << (numFingers > 1 ? "s" : "") << ": ";
|
||||
for (uint32_t f = 0; f < numFingers; f++) {
|
||||
if (f > 0) {
|
||||
if (f == numFingers - 1)
|
||||
msg << " & ";
|
||||
else
|
||||
msg << ", ";
|
||||
}
|
||||
msg << fingers[f]->id;
|
||||
}
|
||||
return msg.str();
|
||||
}
|
||||
|
||||
const std::string printVector(const std::string& name, glm::vec2 v) {
|
||||
std::stringstream msg;
|
||||
msg << name << " (" << v.x << ", " << v.y << ")";
|
||||
return msg.str();
|
||||
}
|
||||
#endif
|
||||
|
||||
[[maybe_unused]] static const char*
|
||||
buttonName(Uint8 button) {
|
||||
switch(button) {
|
||||
case SDL_BUTTON_LEFT: return "left";
|
||||
case SDL_BUTTON_MIDDLE: return "middle";
|
||||
case SDL_BUTTON_RIGHT: return "right";
|
||||
default: return "other";
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
LoadTestSample::doEvent(SDL_Event* event)
|
||||
{
|
||||
switch (event->type) {
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
{
|
||||
SDL_MouseMotionEvent& motion = event->motion;
|
||||
#if LOADTESTSAMPLE_LOG_MOUSE_MOTION_EVENTS
|
||||
SDL_Log("LTS: MOUSE_MOTION - x: %f, y: %f", motion.x, motion.y);
|
||||
#endif
|
||||
// On macOS with trackpad, SDL_TOUCH_MOUSEID is never set.
|
||||
// Prefer mouse events on macOS because press is required. When
|
||||
// finger motion events are used the object starts to rotate when
|
||||
// you drag the cursor over the window. Not nice.
|
||||
if (mouseButtons.left)
|
||||
{
|
||||
rotation.x -= yflip * (mousePos.y - (float)motion.y) * 1.25f;
|
||||
rotation.y -= (mousePos.x - (float)motion.x) * 1.25f;
|
||||
viewChanged();
|
||||
}
|
||||
if (mouseButtons.right)
|
||||
{
|
||||
zoom += (mousePos.y - (float)motion.y) * .005f;
|
||||
viewChanged();
|
||||
}
|
||||
if (mouseButtons.middle)
|
||||
{
|
||||
cameraPos.x -= (mousePos.x - (float)motion.x) * 0.01f;
|
||||
cameraPos.y += yflip * (mousePos.y - (float)motion.y) * 0.01f;
|
||||
viewChanged();
|
||||
}
|
||||
mousePos = glm::vec2((float)motion.x, (float)motion.y);
|
||||
return 0;
|
||||
}
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
mousePos = glm::vec2((float)event->button.x, (float)event->button.y);
|
||||
if (LOADTESTSAMPLE_LOG_MOUSE_UP_DOWN_EVENTS) {
|
||||
SDL_Log("LTS: MOUSE_DOWN - button: %s, x: %f, y: %f", buttonName(event->button.button),
|
||||
event->button.x, event->button.y);
|
||||
}
|
||||
switch (event->button.button) {
|
||||
case SDL_BUTTON_LEFT:
|
||||
mouseButtons.left = true;
|
||||
break;
|
||||
case SDL_BUTTON_MIDDLE:
|
||||
mouseButtons.middle = true;
|
||||
break;
|
||||
case SDL_BUTTON_RIGHT:
|
||||
mouseButtons.right = true;
|
||||
break;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
if (LOADTESTSAMPLE_LOG_MOUSE_UP_DOWN_EVENTS) {
|
||||
SDL_Log("LTS: MOUSE_UP - button: %s, x: %f, y: %f", buttonName(event->button.button),
|
||||
event->button.x, event->button.y);
|
||||
}
|
||||
switch (event->button.button) {
|
||||
case SDL_BUTTON_LEFT:
|
||||
mouseButtons.left = false;
|
||||
break;
|
||||
case SDL_BUTTON_MIDDLE:
|
||||
mouseButtons.middle = false;
|
||||
break;
|
||||
case SDL_BUTTON_RIGHT:
|
||||
mouseButtons.right = false;
|
||||
break;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
case SDL_EVENT_FINGER_DOWN: {
|
||||
// Prevent multifingers from triggering the left button action and
|
||||
// interfering with multigestures.
|
||||
//
|
||||
// On iOS you get a left button down event no matter how many fingers
|
||||
// you touch to the screen. We want 1 finger mouse to work so
|
||||
// behaviour is same as pressing the trackpad on macOS, etc. As iOS
|
||||
// button_down events come before finger_down we can clear the left
|
||||
// button down state, if we have multiple fingers. Hope this ordering
|
||||
// is the same on other touch screen platforms that send a left-button
|
||||
// event regardless of the number of fingers.
|
||||
//
|
||||
// On macOS button_down events come after finger_down so this code has
|
||||
// no effect.
|
||||
//
|
||||
// Another way to handle this is to identify the platform and work
|
||||
// differently for each platform.
|
||||
int numFingers;
|
||||
SDL_Finger** fingers = SDL_GetTouchFingers(event->tfinger.touchID, &numFingers);
|
||||
int retVal = 0;
|
||||
#if LOADTESTSAMPLE_LOG_GESTURE_EVENTS
|
||||
SDL_Log("LTS: Finger: %#" SDL_PRIx64 " down - fingers: %i, %s, x: %f, y: %f",
|
||||
event->tfinger.fingerID, numFingers,
|
||||
printFingerIds(fingers, numFingers).c_str(),
|
||||
event->tfinger.x, event->tfinger.y);
|
||||
#endif
|
||||
if (numFingers > 1) {
|
||||
mouseButtons.left = false;
|
||||
if (LOADTESTSAMPLE_LOG_GESTURE_EVENTS) {
|
||||
SDL_Log("LTS: FINGER_DOWN with multiple fingers received."
|
||||
" Resetting mouseButtons.left.");
|
||||
}
|
||||
if (numFingers == 2) {
|
||||
firstFingerId = fingers[0]->id;
|
||||
// Calc. difference vector between fingers.
|
||||
glm::vec2 vDifference;
|
||||
vDifference.x = fingers[1]->x - fingers[0]->x;
|
||||
vDifference.y = fingers[1]->y - fingers[0]->y;
|
||||
distanceStart = glm::length(vDifference);
|
||||
distanceLast = distanceStart;
|
||||
// Need normalized vectors for glm::orientedAngle
|
||||
nvDifferenceStart = glm::normalize(vDifference);
|
||||
nvDifferenceLast = nvDifferenceStart;
|
||||
processingGesture = true;
|
||||
#if LOADTESTSAMPLE_LOG_GESTURE_EVENTS
|
||||
// Angle of vector to X axis.
|
||||
xAngleStart = atan2f(vDifference.y, vDifference.x);
|
||||
SDL_Log("LTS: FINGER_DOWN, start values: %s, Distance = %f, XAngle = %f°",
|
||||
printVector("Difference", vDifference).c_str(),
|
||||
distanceStart, xAngleStart * 180.0 / M_PI
|
||||
);
|
||||
#endif
|
||||
retVal = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// It is possible to somehow get out of the window without seeing
|
||||
// FINGER_UP so as a safeguard stop any previous gesture.
|
||||
zooming = rotating = false;
|
||||
SDL_free(fingers);
|
||||
return retVal;
|
||||
}
|
||||
case SDL_EVENT_FINGER_UP: {
|
||||
int numFingers;
|
||||
SDL_Finger** fingers = SDL_GetTouchFingers(event->tfinger.touchID, &numFingers);
|
||||
#if LOADTESTSAMPLE_LOG_GESTURE_EVENTS
|
||||
SDL_Log("LTS: Finger: %#" SDL_PRIx64 " up - fingers: %i, %s, x: %f, y: %f",
|
||||
event->tfinger.fingerID, numFingers,
|
||||
printFingerIds(fingers, numFingers).c_str(),
|
||||
event->tfinger.x, event->tfinger.y);
|
||||
#endif
|
||||
if (processingGesture && numFingers == 2) {
|
||||
// There may still be one finger down. Even so the action is completed.
|
||||
if (LOADTESTSAMPLE_LOG_GESTURE_DETECTION) {
|
||||
SDL_Log("-------------- LTS: %s complete. -----------------",
|
||||
zooming ? "zooming" : rotating ? "rotating" : "gesture");
|
||||
}
|
||||
zooming = rotating = processingGesture = false;
|
||||
}
|
||||
SDL_free(fingers);
|
||||
break;
|
||||
}
|
||||
case SDL_EVENT_FINGER_MOTION: {
|
||||
int numFingers;
|
||||
SDL_Finger** fingers = SDL_GetTouchFingers(event->tfinger.touchID, &numFingers);
|
||||
if (numFingers != 2)
|
||||
return 1;
|
||||
if (!processingGesture) {
|
||||
// Protect against FINGER_MOTION without FINGER_DOWN. This can
|
||||
// happen when the sample is switched by a swipe and the new sample
|
||||
// receives the tail end of the swipe motion.
|
||||
return 1;
|
||||
}
|
||||
// With two fingers down, events come in pairs. No point in processing
|
||||
// both.
|
||||
if (event->tfinger.fingerID == firstFingerId) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
glm::vec2 vDifference; // Difference vector between the fingers.
|
||||
vDifference.x = fingers[1]->x - fingers[0]->x;
|
||||
vDifference.y = fingers[1]->y - fingers[0]->y;
|
||||
float distance = glm::length(vDifference);
|
||||
// Normalized vectors required by glm::orientedAngle
|
||||
glm::vec2 nvDifference = glm::normalize(vDifference);
|
||||
// Angle between start and current difference vectors
|
||||
float sAngle = glm::orientedAngle(nvDifferenceStart, nvDifference);
|
||||
// Angle between current and previous difference vectors
|
||||
float dAngle = glm::orientedAngle(nvDifferenceLast, nvDifference);
|
||||
// Difference in distance since last motion event.
|
||||
float dDist = distance - distanceLast;
|
||||
// Difference in distance since start.
|
||||
float dDistStart = distance - distanceStart;
|
||||
#if LOADTESTSAMPLE_LOG_GESTURE_EVENTS
|
||||
if (!(rotating || zooming)) {
|
||||
// Angle from X axis to vDifference vector
|
||||
float xAngle = atan2f(vDifference.y, vDifference.x);
|
||||
SDL_Log("LTS FINGER_MOTION: Not zooming or rotating. "
|
||||
" timestamp = %" SDL_PRIu64 ", %s, %s",
|
||||
event->tfinger.timestamp,
|
||||
printFingerIds(fingers, numFingers).c_str(),
|
||||
printVector("Difference", vDifference).c_str());
|
||||
SDL_Log("... distanceLast = %f, distance = %f, dDist = %f, dDistStart = %f, xAngle = %f°, sAngle = %f°, dAngle = %f°",
|
||||
distanceLast, distance, dDist, dDistStart,
|
||||
xAngle * 180.0 / M_PI, sAngle * 180.0 / M_PI, dAngle * 180.0 / M_PI);
|
||||
}
|
||||
#endif
|
||||
nvDifferenceLast = nvDifference;
|
||||
distanceLast = distance;
|
||||
|
||||
// This is all heuristics derived from use.
|
||||
if (zooming) {
|
||||
zoom += dDist * 10.0f;
|
||||
if (LOADTESTSAMPLE_LOG_GESTURE_EVENTS) {
|
||||
SDL_Log("LTS MG: Zooming. zoom = %f", zoom);
|
||||
}
|
||||
} else if (!rotating) {
|
||||
if (fabs(dDistStart) >= 0.1 && fabs(dAngle) < 0.5 * M_PI / 180.0) {
|
||||
zooming = true;
|
||||
zoom += dDist * 10.0f;
|
||||
if (LOADTESTSAMPLE_LOG_GESTURE_DETECTION) {
|
||||
SDL_Log("---------------- LTS MG: pinch/zoom detected ---------------\n"
|
||||
" dAngle = %f°, dDistStart = %f, dDist = %f, zoom = %f",
|
||||
dAngle * 180.0 / M_PI, dDistStart, dDist, zoom);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rotating) {
|
||||
rotation.z +=
|
||||
static_cast<float>(dAngle * 180.0 / M_PI);
|
||||
if (LOADTESTSAMPLE_LOG_GESTURE_EVENTS) {
|
||||
SDL_Log("LTS MG: Rotating around Z. rotation.z = %f°", rotation.z);
|
||||
}
|
||||
} else if (!zooming) {
|
||||
if (fabs(sAngle) > 15 * M_PI / 180.0 && fabs(dDistStart) < 0.1) {
|
||||
rotating = true;
|
||||
rotation.z += static_cast<float>(dAngle * 180.0 / M_PI);
|
||||
if (LOADTESTSAMPLE_LOG_GESTURE_DETECTION) {
|
||||
SDL_Log("---------------- LTS MG: rotation detected ---------------\n"
|
||||
" sAngle = %f°, dAngle = %f°, dDistStart = %f, rotation.z = %f°",
|
||||
sAngle * 180 / M_PI, dAngle * 180.0 / M_PI, dDistStart, rotation.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
viewChanged();
|
||||
SDL_free(fingers);
|
||||
return 0;
|
||||
}
|
||||
case SDL_EVENT_KEY_UP:
|
||||
if (event->key.key == 'q')
|
||||
quit = true;
|
||||
keyPressed(event->key.key);
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2017-2020 Mark Callow, <khronos at callow dot im>.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _LOAD_TEST_SAMPLE_H
|
||||
#define _LOAD_TEST_SAMPLE_H
|
||||
|
||||
#include <string>
|
||||
#include <SDL3/SDL.h>
|
||||
#define GLM_FORCE_RADIANS
|
||||
#include "disable_glm_warnings.h"
|
||||
#include <glm/glm.hpp>
|
||||
#include "reenable_warnings.h"
|
||||
|
||||
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
|
||||
|
||||
class LoadTestSample {
|
||||
public:
|
||||
typedef uint64_t ticks_t;
|
||||
LoadTestSample(uint32_t width, uint32_t height,
|
||||
const std::string sBasePath,
|
||||
int32_t yflip = 1)
|
||||
: w_width(width), w_height(height), yflip(yflip),
|
||||
sBasePath(sBasePath)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~LoadTestSample() { };
|
||||
virtual int doEvent(SDL_Event* event);
|
||||
virtual void resize(uint32_t width, uint32_t height) = 0;
|
||||
virtual void run(uint32_t msTicks) = 0;
|
||||
|
||||
//virtual void getOverlayText(TextOverlay *textOverlay) { };
|
||||
|
||||
typedef LoadTestSample* (*PFN_create)(uint32_t width, uint32_t height,
|
||||
const char* const szArgs,
|
||||
const std::string sBasePath);
|
||||
|
||||
protected:
|
||||
virtual void keyPressed(uint32_t /*keyCode*/) { }
|
||||
virtual void viewChanged() { }
|
||||
|
||||
const std::string getAssetPath() { return sBasePath; }
|
||||
|
||||
glm::vec3 rotation;
|
||||
glm::vec3 cameraPos;
|
||||
glm::vec2 mousePos;
|
||||
glm::vec2 nvDifferenceStart; // Normalized difference between fingers at start of gesture.
|
||||
float distanceStart = 0.0; // Distance between fingers at start of gesture.
|
||||
float xAngleStart = 0.0; // Angle between x-axis and nvDifferenceStart. Unused unless event logging is enabled.
|
||||
glm::vec2 nvDifferenceLast; // Normalized difference between fingers at last motion event.
|
||||
float distanceLast = 0.0; // Distance between fingers at last motion event.
|
||||
Uint64 firstFingerId = 0;
|
||||
bool processingGesture = false;
|
||||
|
||||
struct {
|
||||
bool left = false;
|
||||
bool right = false;
|
||||
bool middle = false;
|
||||
} mouseButtons;
|
||||
bool quit = false;
|
||||
bool rotating = false;
|
||||
bool zooming = false;
|
||||
bool paused = false;
|
||||
|
||||
float zoom = 0;
|
||||
|
||||
uint32_t w_width;
|
||||
uint32_t w_height;
|
||||
|
||||
// Defines a frame rate independent timer value clamped from -1.0...1.0
|
||||
// For use in animations, rotations, etc.
|
||||
float timer = 0.0f;
|
||||
// Multiplier for speeding up (or slowing down) the global timer
|
||||
float timerSpeed = 0.25f;
|
||||
|
||||
// Use to adjust mouse rotation speed
|
||||
float rotationSpeed = 1.0f;
|
||||
// Use to adjust mouse zoom speed
|
||||
float zoomSpeed = 1.0f;
|
||||
// multiplier to decide if Y increases down or up
|
||||
int32_t yflip;
|
||||
|
||||
const std::string sBasePath;
|
||||
};
|
||||
|
||||
#endif /* _LOAD_TEST_SAMPLE_H */
|
||||
@@ -0,0 +1,146 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2017-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @class SwipeDetector
|
||||
* @~English
|
||||
*
|
||||
* @brief Definition of a class for detecting swipes.
|
||||
*
|
||||
* @author Mark Callow, github.com/MarkCallow.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include "SwipeDetector.h"
|
||||
#include <SDL3/SDL_log.h>
|
||||
|
||||
#if !defined(SWIPEDETECTOR_LOG_GESTURE_EVENTS)
|
||||
#define SWIPEDETECTOR_LOG_GESTURE_EVENTS 0
|
||||
#endif
|
||||
#if !defined(SWIPEDETECTOR_LOG_GESTURE_DETECTION)
|
||||
#define SWIPEDETECTOR_LOG_GESTURE_DETECTION 0
|
||||
#endif
|
||||
|
||||
bool
|
||||
SwipeDetector::doEvent(SDL_Event* event)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
switch (event->type) {
|
||||
case SDL_EVENT_FINGER_UP: {
|
||||
int numFingers;
|
||||
SDL_Finger** fingers = SDL_GetTouchFingers(event->tfinger.touchID, &numFingers);
|
||||
if (SWIPEDETECTOR_LOG_GESTURE_EVENTS) {
|
||||
SDL_Log("SD: Finger: %" SDL_PRIx64 " UP - fingers: %i, x: %f, y: %f",
|
||||
event->tfinger.fingerID, numFingers, event->tfinger.x, event->tfinger.y);
|
||||
}
|
||||
// SDL_GetTouchFingers appears to return the number of fingers
|
||||
// down *before* the event was generated, so 1 means the last finger
|
||||
// just lifted.
|
||||
if (numFingers == 1 && gestureStart.time != 0) {
|
||||
gestureStart.time = 0;
|
||||
gestureSwipe = false;
|
||||
if (SWIPEDETECTOR_LOG_GESTURE_DETECTION) {
|
||||
SDL_Log("***************** SD: FINGER_UP, %smultigesture done *****************",
|
||||
gestureSwipe ? "Swipe complete & " : "");
|
||||
}
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
SDL_free(fingers);
|
||||
break;
|
||||
}
|
||||
case GESTURE_MULTIGESTURE: {
|
||||
Gesture_MultiGestureEvent& mgesture = *(Gesture_MultiGestureEvent *)event;
|
||||
if (SWIPEDETECTOR_LOG_GESTURE_EVENTS) {
|
||||
SDL_Log("SD: MG Event: x = %f, y = %f, dAng = %f (%f), dR = %f, numFingers = %i, time = %" SDL_PRIu64,
|
||||
mgesture.x,
|
||||
mgesture.y,
|
||||
mgesture.dTheta * 180.0 / M_PI,
|
||||
mgesture.dTheta,
|
||||
mgesture.dDist,
|
||||
mgesture.numFingers,
|
||||
mgesture.timestamp);
|
||||
}
|
||||
if (SWIPEDETECTOR_LOG_GESTURE_DETECTION) {
|
||||
SDL_Log("SD: mgestureSwipe = %i, time = %" SDL_PRIu64,
|
||||
gestureSwipe,
|
||||
(mgesture.timestamp - gestureStart.time) / 1000000);
|
||||
}
|
||||
if (gestureStart.time == 0) {
|
||||
if (SWIPEDETECTOR_LOG_GESTURE_DETECTION) {
|
||||
SDL_Log("************ SD: Multigesture detection start **************");
|
||||
}
|
||||
gestureStart.time = mgesture.timestamp;
|
||||
gestureStart.point.x = mgesture.x;
|
||||
gestureStart.point.y = mgesture.y;
|
||||
lastVector.reset();
|
||||
gestureSwipe = false;
|
||||
} else {
|
||||
if (!gestureSwipe) {
|
||||
vector sv; // Vector from start point to current position
|
||||
float velocity;
|
||||
float theta; // Angle between current vector and previous vector.
|
||||
float duration;
|
||||
sv.w = mgesture.x - gestureStart.point.x;
|
||||
sv.h = mgesture.y - gestureStart.point.y;
|
||||
float distance = sv.length();
|
||||
if (lastVector.has_value()) {
|
||||
// SDL2 timestamps were in milliseconds, SDL3 are nanoseconds. Given the
|
||||
// normalized distances reported, using nanoseconds leads to 0 velocitySq.
|
||||
duration = static_cast<float>(
|
||||
(mgesture.timestamp - gestureStart.time) / 1000000.0);
|
||||
|
||||
velocity = distance / duration;
|
||||
assert(!std::isinf(velocity));
|
||||
theta = static_cast<float>(lastVector->getAngle(sv));
|
||||
if (SWIPEDETECTOR_LOG_GESTURE_DETECTION) {
|
||||
SDL_Log("SD: Detection: distance = %f, velocity = %f, theta = %f, sv angle = %f, sv angle normalized = %f, lastv angle = %f",
|
||||
distance, velocity, theta,
|
||||
sv.getAngle(),
|
||||
sv.getAngleNormalized(),
|
||||
lastVector->getAngle());
|
||||
}
|
||||
lastVector = sv;
|
||||
// Multiple events with the same timestamp is a possibility
|
||||
// hence the isinf() check.
|
||||
if (std::abs(theta) < 3.0 && std::abs(mgesture.dDist) > 0.01 && !std::isinf(velocity) && velocity > 0.0007) {
|
||||
if (SWIPEDETECTOR_LOG_GESTURE_DETECTION)
|
||||
SDL_Log("----------------- SD: Swipe %s detected -----------------",
|
||||
toString(sv.getDirection()).c_str());
|
||||
gestureSwipe = true;
|
||||
if (SDL_EventEnabled(SDL_EVENT_USER)) {
|
||||
SDL_Event user_event;
|
||||
// SDL will copy this entire struct! Initialize to keep memory
|
||||
// checkers happy.
|
||||
SDL_zero(user_event);
|
||||
user_event.type = SDL_EVENT_USER;
|
||||
user_event.user.code = swipeGesture;
|
||||
user_event.user.data1 = SwipeDetector::directionToPointer(sv.getDirection());
|
||||
user_event.user.data2 = NULL;
|
||||
SDL_PushEvent(&user_event);
|
||||
}
|
||||
} else {
|
||||
if (SWIPEDETECTOR_LOG_GESTURE_DETECTION) SDL_Log("SD: No swipe detected.");
|
||||
result = true;
|
||||
}
|
||||
} else {
|
||||
lastVector = sv;
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2017-2020 Mark Callow, <khronos at callow dot im>.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _SWIPE_DETECTOR_H
|
||||
#define _SWIPE_DETECTOR_H
|
||||
#if defined(_WIN32)
|
||||
#define _USE_MATH_DEFINES
|
||||
#endif
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <math.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include "SDL_gesture.h"
|
||||
|
||||
class SwipeDetector {
|
||||
public:
|
||||
enum class Direction { up, down, left, right };
|
||||
|
||||
SwipeDetector() : gestureSwipe(false) {}
|
||||
bool doEvent(SDL_Event* event);
|
||||
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
// Not clangcl
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4311)
|
||||
#pragma warning(disable : 4302)
|
||||
#pragma warning(disable : 4312)
|
||||
#endif
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wvoid-pointer-to-int-cast"
|
||||
#endif
|
||||
// These conversions allow storing a Direction in a pointer.
|
||||
// Ugly. Horrible. But preferable to allocating and freeing memory
|
||||
// when passing the information in user events.
|
||||
static inline Direction pointerToDirection(void* p) {
|
||||
return static_cast<SwipeDetector::Direction>(reinterpret_cast<long>(p));
|
||||
}
|
||||
|
||||
// Only preserves the low 32-bits of the pointer; perfect for this use.
|
||||
static inline void* directionToPointer(SwipeDetector::Direction d) {
|
||||
return reinterpret_cast<void*>(static_cast<long>(d));
|
||||
}
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
static const Uint32 swipeGesture = 0x01;
|
||||
|
||||
class vector {
|
||||
public:
|
||||
float w;
|
||||
float h;
|
||||
|
||||
vector() : w(0.0), h(0.0) { }
|
||||
vector(float _w, float _h) : w(_w), h(_h) { }
|
||||
/**
|
||||
* @~English
|
||||
* @internal
|
||||
* @brief Find the angle between the vector and the X-axis
|
||||
*
|
||||
* Positive angles increase counter-clockwise from the X-axis
|
||||
* which has +x to the right.
|
||||
*
|
||||
* @return the angle between the vector and the x axis in degrees.
|
||||
*/
|
||||
double getAngle() {
|
||||
double rad = atan2(h, w);
|
||||
return rad * 180/M_PI;
|
||||
}
|
||||
|
||||
/**
|
||||
* @~English
|
||||
* @internal
|
||||
* @brief Find the angle between this vector and another.
|
||||
*
|
||||
* Positive angles increase counter-clockwise.
|
||||
*
|
||||
* @return the angle between the 2 vectors in degrees.
|
||||
*/
|
||||
double getAngle(const vector& v2) {
|
||||
// Reputed to be more accurate but in our use so far both approaches give same answer.
|
||||
//double rad = atan2f(v2.h, v2.w) - SDL_atan2(h, w);
|
||||
double rad = atan2f(w * v2.h - h * v2.w, w * v2.w + h * v2.h);
|
||||
return rad * 180/M_PI;
|
||||
}
|
||||
|
||||
/**
|
||||
* @~English
|
||||
* @internal
|
||||
* @brief Find the angle between the vector and the X-axis
|
||||
*
|
||||
* Positive angles increase counter-clockwise from the X-axis
|
||||
* which has +x to the right. Value is normalized to the range 0 to 360.
|
||||
*
|
||||
* @return the angle between the vector and the x axis in degrees.
|
||||
*/
|
||||
double getAngleNormalized() {
|
||||
double rad = atan2(h, w) + M_PI;
|
||||
return fmod(rad*180/M_PI + 180, 360);
|
||||
}
|
||||
|
||||
/**
|
||||
* @~English
|
||||
* @internal
|
||||
* @brief Return the length of the vector.
|
||||
*
|
||||
* @return the length of the vector.
|
||||
*/
|
||||
float length() { return sqrt(w * w + h * h); }
|
||||
|
||||
/**
|
||||
* @~English
|
||||
* @internal
|
||||
* @brief Return the direction of the vector.
|
||||
*
|
||||
* @return the direction
|
||||
*/
|
||||
Direction getDirection() {
|
||||
double angle = getAngleNormalized();
|
||||
return getDirection(angle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @~English
|
||||
* @internal
|
||||
* @brief Return a direction given an angle.
|
||||
*
|
||||
* Directions are defined as follows:
|
||||
*
|
||||
* Up: [45, 135]
|
||||
* Right: [0,45] and [315, 360]
|
||||
* Down: [225, 315]
|
||||
* Left: [135, 225]
|
||||
*
|
||||
* @param angle an angle from 0 to 360°
|
||||
* @return the direction of an angle
|
||||
*/
|
||||
static Direction getDirection(double angle){
|
||||
if (inRange(angle, 45, 135)) {
|
||||
return Direction::down;
|
||||
} else if (inRange(angle, 0, 45) || inRange(angle, 315, 360)) {
|
||||
return Direction::right;
|
||||
} else if (inRange(angle, 225, 315)) {
|
||||
return Direction::up;
|
||||
} else {
|
||||
return Direction::left;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @~English
|
||||
* @internal
|
||||
* @brief Check if angle falls within an interval.
|
||||
*
|
||||
* @param angle an angle
|
||||
* @param init the initial bound
|
||||
* @param end the final bound
|
||||
*
|
||||
* @return true if the given angle is in the interval [init, end), false
|
||||
* otherwise.
|
||||
*/
|
||||
static bool inRange(double angle, float init, float end){
|
||||
return (angle >= init) && (angle < end);
|
||||
}
|
||||
};
|
||||
|
||||
protected:
|
||||
struct gestureStart {
|
||||
Uint64 time;
|
||||
SDL_FPoint point;
|
||||
gestureStart() { time = 0; point.x = point.y = 0.0; }
|
||||
} gestureStart;
|
||||
std::optional<vector> lastVector;
|
||||
bool gestureSwipe;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline std::string toString(SwipeDetector::Direction dir) {
|
||||
switch (dir) {
|
||||
case SwipeDetector::Direction::up: return "up";
|
||||
case SwipeDetector::Direction::down: return "down";
|
||||
case SwipeDetector::Direction::left: return "left";
|
||||
case SwipeDetector::Direction::right: return "right";
|
||||
// This is to hide a warning from MSVC. According to the solution given
|
||||
// in https://developercommunity.visualstudio.com/t/Visual-Studio-warning-on-Strongly-typed-/96302
|
||||
// it is possible to construct an enum class with any value. Thus warning.
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _SWIPE_DETECTOR_H */
|
||||
@@ -0,0 +1,61 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2017-2020 Mark Callow, <khronos at callow dot im>.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <ktx.h>
|
||||
#include "TranscodeTargetStrToFmt.h"
|
||||
|
||||
ktx_transcode_fmt_e
|
||||
TranscodeTargetStrToFmt(std::string format)
|
||||
{
|
||||
if (!format.compare("ETC1_RGB"))
|
||||
return KTX_TTF_ETC1_RGB;
|
||||
else if (!format.compare("ETC2_RGBA"))
|
||||
return KTX_TTF_ETC2_RGBA;
|
||||
else if (!format.compare("BC1_RGB"))
|
||||
return KTX_TTF_BC1_RGB;
|
||||
else if (!format.compare("BC3_RGBA"))
|
||||
return KTX_TTF_BC3_RGBA;
|
||||
else if (!format.compare("BC4_R"))
|
||||
return KTX_TTF_BC4_R;
|
||||
else if (!format.compare("BC5_RG"))
|
||||
return KTX_TTF_BC5_RG;
|
||||
else if (!format.compare("BC7_M6_RGB"))
|
||||
return KTX_TTF_BC7_M6_RGB;
|
||||
else if (!format.compare("BC7_M5_RGBA"))
|
||||
return KTX_TTF_BC7_M5_RGBA;
|
||||
else if (!format.compare("PVRTC1_4_RGB"))
|
||||
return KTX_TTF_PVRTC1_4_RGB;
|
||||
else if (!format.compare("PVRTC1_4_RGBA"))
|
||||
return KTX_TTF_PVRTC1_4_RGBA;
|
||||
else if (!format.compare("ASTC_4x4_RGBA"))
|
||||
return KTX_TTF_ASTC_4x4_RGBA;
|
||||
else if (!format.compare("PVRTC2_4_RGB"))
|
||||
return KTX_TTF_PVRTC2_4_RGB;
|
||||
else if (!format.compare("PVRTC2_4_RGBA"))
|
||||
return KTX_TTF_PVRTC2_4_RGBA;
|
||||
else if (!format.compare("ETC2_EAC_R11"))
|
||||
return KTX_TTF_ETC2_EAC_R11;
|
||||
else if (!format.compare("ETC2_EAC_RG11"))
|
||||
return KTX_TTF_ETC2_EAC_RG11;
|
||||
else if (!format.compare("RGBA32"))
|
||||
return KTX_TTF_RGBA32;
|
||||
else if (!format.compare("RGB565"))
|
||||
return KTX_TTF_RGB565;
|
||||
else if (!format.compare("BGR565"))
|
||||
return KTX_TTF_BGR565;
|
||||
else if (!format.compare("RGBA4444"))
|
||||
return KTX_TTF_RGBA4444;
|
||||
else if (!format.compare("ETC"))
|
||||
return KTX_TTF_ETC;
|
||||
else if (!format.compare("BC1_OR_3"))
|
||||
return KTX_TTF_BC1_OR_3;
|
||||
assert(false); // Error in args in sample table.
|
||||
return static_cast<ktx_transcode_fmt_e>(-1); // To keep compilers happy.
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2017-2020 Mark Callow, <khronos at callow dot im>.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _TRANSCODE_TARGET_STR_TO_FMT_
|
||||
#define _TRANSCODE_TARGET_STR_TO_FMT_
|
||||
|
||||
#include <string>
|
||||
|
||||
ktx_transcode_fmt_e TranscodeTargetStrToFmt(std::string format);
|
||||
|
||||
#endif /* _TRANSCODE_TARGET_STR_TO_FMT_ */
|
||||
@@ -0,0 +1,21 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2021 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#if !defined DISABLE_GLM_WARNINGS_H
|
||||
|
||||
// Temporarily disable the warnings caused by the GLM code.
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4201)
|
||||
#elif defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgnu-anonymous-struct"
|
||||
#pragma clang diagnostic ignored "-Wnested-anon-types"
|
||||
#endif
|
||||
|
||||
#endif /* DISABLE_GLM_WARNINGS_H */
|
||||
@@ -0,0 +1,66 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2018-2020 Mark Callow, <khronos at callow dot im>.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @file
|
||||
* @~English
|
||||
*
|
||||
* @brief Custom exceptions for the load tests.
|
||||
*/
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#define OUT_OF_HOST_MEMORY -1
|
||||
#define OUT_OF_DEVICE_MEMORY -2
|
||||
#define FRAGMENTED_POOL -12
|
||||
#define OUT_OF_POOL_MEMORY -1000069000
|
||||
|
||||
class bad_vulkan_alloc : public std::bad_alloc {
|
||||
public:
|
||||
bad_vulkan_alloc(int which, const char* _message) : std::bad_alloc() {
|
||||
if (which == FRAGMENTED_POOL) {
|
||||
message << "Pool fragmented when allocating for " << _message << ".";
|
||||
} else {
|
||||
std::string memtype;
|
||||
switch (which) {
|
||||
case OUT_OF_HOST_MEMORY: memtype = "host"; break;
|
||||
case OUT_OF_DEVICE_MEMORY: memtype = "device"; break;
|
||||
case OUT_OF_POOL_MEMORY: memtype = "pool"; break;
|
||||
default: break;
|
||||
}
|
||||
message << "Out of " << memtype << " memory for " << _message << ".";
|
||||
}
|
||||
_what = message.str();
|
||||
}
|
||||
bad_vulkan_alloc(const bad_vulkan_alloc& in)
|
||||
: std::bad_alloc()
|
||||
, message{in.message.str()}
|
||||
, _what{in._what}
|
||||
{}
|
||||
virtual const char* what() const throw() {
|
||||
return _what.c_str();
|
||||
}
|
||||
protected:
|
||||
std::stringstream message;
|
||||
std::string _what;
|
||||
};
|
||||
|
||||
class unsupported_ttype : public std::runtime_error {
|
||||
public:
|
||||
unsupported_ttype()
|
||||
: std::runtime_error("Implementation does not support needed operations on image format") { }
|
||||
unsupported_ttype(std::string& message) : std::runtime_error(message) { }
|
||||
};
|
||||
|
||||
class unsupported_ctype : public std::runtime_error {
|
||||
public:
|
||||
unsupported_ctype()
|
||||
: std::runtime_error("Unsupported compression format") { }
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
# Copyright 2020 The Khronos Group Inc.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Max2Obj Version 4.0 Mar 10th, 2001
|
||||
#
|
||||
# object default to come ...
|
||||
#
|
||||
v -5.000000 -5.000000 -5.000000
|
||||
v 5.000000 -5.000000 -5.000000
|
||||
v -5.000000 5.000000 -5.000000
|
||||
v 5.000000 5.000000 -5.000000
|
||||
v -5.000000 -5.000000 5.000000
|
||||
v 5.000000 -5.000000 5.000000
|
||||
v -5.000000 5.000000 5.000000
|
||||
v 5.000000 5.000000 5.000000
|
||||
# 8 vertices
|
||||
|
||||
vt 0.000000 0.000000 0.000000
|
||||
vt 1.000000 0.000000 0.000000
|
||||
vt 0.000000 1.000000 0.000000
|
||||
vt 1.000000 1.000000 0.000000
|
||||
vt 0.000000 0.000000 0.000000
|
||||
vt 1.000000 0.000000 0.000000
|
||||
vt 0.000000 1.000000 0.000000
|
||||
vt 1.000000 1.000000 0.000000
|
||||
vt 0.000000 0.000000 0.000000
|
||||
vt 1.000000 0.000000 0.000000
|
||||
vt 0.000000 1.000000 0.000000
|
||||
vt 1.000000 1.000000 0.000000
|
||||
# 12 texture vertices
|
||||
|
||||
vn 0.000000 0.000000 -2.000000
|
||||
vn 0.000000 0.000000 -1.000000
|
||||
vn 0.000000 0.000000 -1.000000
|
||||
vn 0.000000 0.000000 -2.000000
|
||||
vn 0.000000 -0.000000 2.000000
|
||||
vn 0.000000 -0.000000 1.000000
|
||||
vn 0.000000 -0.000000 1.000000
|
||||
vn 0.000000 -0.000000 2.000000
|
||||
# 8 vertex normals
|
||||
|
||||
g default
|
||||
s 2
|
||||
f 1/10/1 3/12/3 4/11/4
|
||||
f 4/11/4 2/9/2 1/10/1
|
||||
s 4
|
||||
f 5/9/5 6/10/6 8/12/8
|
||||
f 8/12/8 7/11/7 5/9/5
|
||||
s 8
|
||||
f 1/5/1 2/6/2 6/8/6
|
||||
f 6/8/6 5/7/5 1/5/1
|
||||
s 16
|
||||
f 2/1/2 4/2/4 8/4/8
|
||||
f 8/4/8 6/3/6 2/1/2
|
||||
s 32
|
||||
f 4/5/4 3/6/3 7/8/7
|
||||
f 7/8/7 8/7/8 4/5/4
|
||||
s 64
|
||||
f 3/1/3 1/2/1 5/4/5
|
||||
f 5/4/5 7/3/7 3/1/3
|
||||
# 12 faces
|
||||
|
||||
g
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2021 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#if !defined REENABLE_WARNINGS_H
|
||||
|
||||
// Reenable warnings disabled by, e.g. disable_glm_warnings.h.
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#elif defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#endif /* REENABLE_WARNINGS_H */
|
||||
@@ -0,0 +1,448 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2016-2020 Mark Callow
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef VECMATH_9B7E1CFE346D11E6AFA2D7DC87495A69_H
|
||||
#define VECMATH_9B7E1CFE346D11E6AFA2D7DC87495A69_H
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @~English
|
||||
*
|
||||
* @brief Vector math package modelled after GLSL.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
struct vec2 {
|
||||
// Anonymous unions are portable.
|
||||
union {
|
||||
float x;
|
||||
float r;
|
||||
};
|
||||
union {
|
||||
float y;
|
||||
float g;
|
||||
};
|
||||
|
||||
vec2() { };
|
||||
};
|
||||
|
||||
struct vec3 {
|
||||
union {
|
||||
float x;
|
||||
float r;
|
||||
};
|
||||
union {
|
||||
float y;
|
||||
float g;
|
||||
};
|
||||
union {
|
||||
float z;
|
||||
float b;
|
||||
};
|
||||
|
||||
vec3() { }
|
||||
|
||||
vec3(float x, float y, float z) : x(x), y(y), z(z) { }
|
||||
|
||||
vec3(const vec3& value)
|
||||
: x(value.x), y(value.y), z(value.z) { }
|
||||
|
||||
vec3 operator-() const
|
||||
{
|
||||
return vec3(-x, -y, -z);
|
||||
}
|
||||
|
||||
vec3 operator-(const vec3& value) const
|
||||
{
|
||||
return vec3(this->x - value.x, this->y - value.y, this->z - value.z);
|
||||
}
|
||||
|
||||
vec3 operator/(float divisor) const
|
||||
{
|
||||
return vec3(x / divisor, y / divisor, z / divisor);
|
||||
}
|
||||
|
||||
vec3& operator/=(float divisor)
|
||||
{
|
||||
x /= divisor;
|
||||
y /= divisor;
|
||||
z /= divisor;
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec3& operator=(const vec3& value)
|
||||
{
|
||||
x = value.x;
|
||||
y = value.y;
|
||||
z = value.z;
|
||||
return *this;
|
||||
}
|
||||
|
||||
float operator[](int i) const
|
||||
{
|
||||
switch (i) {
|
||||
case 0: return x;
|
||||
case 1: return y;
|
||||
case 2: return z;
|
||||
default:
|
||||
assert(false);
|
||||
return z;
|
||||
}
|
||||
}
|
||||
|
||||
float& operator[](int i)
|
||||
{
|
||||
switch (i) {
|
||||
case 0: return x;
|
||||
case 1: return y;
|
||||
case 2: return z;
|
||||
default:
|
||||
assert(false);
|
||||
return z;
|
||||
}
|
||||
}
|
||||
|
||||
vec3 cross(const vec3& value) const
|
||||
{
|
||||
return vec3::cross(*this, value);
|
||||
}
|
||||
|
||||
float dot(const vec3& vec) const
|
||||
{
|
||||
return vec3::dot(*this, vec);
|
||||
}
|
||||
|
||||
float length() const
|
||||
{
|
||||
return sqrt(dot(*this));
|
||||
}
|
||||
|
||||
vec3& normalize()
|
||||
{
|
||||
float length = this->length();
|
||||
return (length > 0.0f ? *this /= length : *this);
|
||||
}
|
||||
|
||||
static vec3 cross(const vec3& a, const vec3& b)
|
||||
{
|
||||
return vec3(a.y * b.z - a.z * b.y,
|
||||
a.z * b.x - a.x * b.z,
|
||||
a.x * b.y - a.y * b.x);
|
||||
}
|
||||
|
||||
static float dot(const vec3& a, const vec3& b)
|
||||
{
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z;
|
||||
}
|
||||
|
||||
static vec3 normalize (const vec3& input)
|
||||
{
|
||||
float length = input.length();
|
||||
return (length > 0.0f ? input / length : input);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct vec4 {
|
||||
|
||||
union {
|
||||
float x;
|
||||
float r;
|
||||
};
|
||||
union {
|
||||
float y;
|
||||
float g;
|
||||
};
|
||||
union {
|
||||
float z;
|
||||
float b;
|
||||
};
|
||||
union {
|
||||
float w;
|
||||
float a;
|
||||
};
|
||||
|
||||
vec4() { }
|
||||
|
||||
vec4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) { }
|
||||
|
||||
vec4(const vec3& value, float w)
|
||||
: x(value.x), y(value.y), z(value.z), w(w) { }
|
||||
|
||||
vec4(const vec4& value)
|
||||
: x(value.x), y(value.y), z(value.z), w(value.w) { }
|
||||
|
||||
vec4 operator/(float divisor) const
|
||||
{
|
||||
return vec4(x / divisor, y / divisor, z / divisor, w / divisor);
|
||||
}
|
||||
|
||||
vec4& operator/=(float divisor)
|
||||
{
|
||||
x /= divisor;
|
||||
y /= divisor;
|
||||
z /= divisor;
|
||||
w /= divisor;
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec4 operator*(float multiplicand) const
|
||||
{
|
||||
return vec4(x * multiplicand, y * multiplicand, z * multiplicand,
|
||||
w * multiplicand);
|
||||
}
|
||||
|
||||
vec4& operator=(const vec4& value)
|
||||
{
|
||||
x = value.x;
|
||||
y = value.y;
|
||||
z = value.z;
|
||||
w = value.w;
|
||||
return *this;
|
||||
}
|
||||
|
||||
float operator[](int i) const
|
||||
{
|
||||
switch (i) {
|
||||
case 0: return x;
|
||||
case 1: return y;
|
||||
case 2: return z;
|
||||
case 3: return w;
|
||||
default:
|
||||
assert(false);
|
||||
return z;
|
||||
}
|
||||
}
|
||||
|
||||
float& operator[](int i)
|
||||
{
|
||||
switch (i) {
|
||||
case 0: return x;
|
||||
case 1: return y;
|
||||
case 2: return z;
|
||||
case 3: return w;
|
||||
default:
|
||||
assert(false);
|
||||
return z;
|
||||
}
|
||||
}
|
||||
|
||||
float dot(const vec4& vec) const
|
||||
{
|
||||
return vec4::dot(*this, vec);
|
||||
}
|
||||
|
||||
float length() const
|
||||
{
|
||||
return sqrt(dot(*this));
|
||||
}
|
||||
|
||||
static float dot(const vec4& a, const vec4& b)
|
||||
{
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
|
||||
}
|
||||
|
||||
static vec4 normalize (const vec4& input)
|
||||
{
|
||||
float length = input.length();
|
||||
return (length > 0.0f ? input / length : input);
|
||||
}
|
||||
};
|
||||
|
||||
struct mat3 {
|
||||
vec3 m[3];
|
||||
|
||||
mat3()
|
||||
{
|
||||
m[0] = vec3(1.0f, 0.0f, 0.0f);
|
||||
m[1] = vec3(0.0f, 1.0f, 0.0f);
|
||||
m[2] = vec3(0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
mat3(const vec3& value1, const vec3& value2, const vec3& value3)
|
||||
{
|
||||
m[0] = value1;
|
||||
m[1] = value2;
|
||||
m[2] = value3;
|
||||
}
|
||||
|
||||
mat3 transpose() const
|
||||
{
|
||||
return mat3(vec3(m[0].x, m[1].x, m[2].x),
|
||||
vec3(m[0].y, m[1].y, m[2].y),
|
||||
vec3(m[0].z, m[1].z, m[2].z));
|
||||
}
|
||||
};
|
||||
|
||||
struct mat4 {
|
||||
vec4 m[4];
|
||||
|
||||
mat4()
|
||||
{
|
||||
m[0] = vec4(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
m[1] = vec4(0.0f, 1.0f, 0.0f, 0.0f);
|
||||
m[2] = vec4(0.0f, 0.0f, 1.0f, 0.0f);
|
||||
m[3] = vec4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
mat4(const vec4& value1, const vec4& value2,
|
||||
const vec4& value3, const vec4& value4)
|
||||
{
|
||||
m[0] = value1;
|
||||
m[1] = value2;
|
||||
m[2] = value3;
|
||||
m[3] = value4;
|
||||
}
|
||||
|
||||
mat4 operator*(float value) const
|
||||
{
|
||||
return mat4(m[0] * value, m[1] * value, m[2] * value, m[3] * value);
|
||||
}
|
||||
|
||||
mat4 operator*(const mat4& value) const
|
||||
{
|
||||
mat4 right = value.transpose();
|
||||
return mat4(
|
||||
vec4(m[0].dot(right.m[0]), m[0].dot(right.m[1]),
|
||||
m[0].dot(right.m[2]), m[0].dot(right.m[3])),
|
||||
vec4(m[1].dot(right.m[0]), m[1].dot(right.m[1]),
|
||||
m[1].dot(right.m[2]), m[1].dot(right.m[3])),
|
||||
vec4(m[2].dot(right.m[0]), m[2].dot(right.m[1]),
|
||||
m[2].dot(right.m[2]), m[2].dot(right.m[3])),
|
||||
vec4(m[3].dot(right.m[0]), m[3].dot(right.m[1]),
|
||||
m[3].dot(right.m[2]), m[3].dot(right.m[3]))
|
||||
);
|
||||
}
|
||||
|
||||
vec4 operator*(const vec4& value) const
|
||||
{
|
||||
return vec4(m[0].dot(value), m[1].dot(value),
|
||||
m[2].dot(value), m[3].dot(value));
|
||||
}
|
||||
|
||||
vec4& operator[](int i)
|
||||
{
|
||||
return m[i];
|
||||
}
|
||||
|
||||
mat4 transpose() const
|
||||
{
|
||||
return mat4(vec4(m[0][0], m[1][0], m[2][0], m[3][0]),
|
||||
vec4(m[0][1], m[1][1], m[2][1], m[3][1]),
|
||||
vec4(m[0][2], m[1][2], m[2][2], m[3][2]),
|
||||
vec4(m[0][3], m[1][3], m[2][3], m[3][3]));
|
||||
}
|
||||
|
||||
static mat4 translate(const vec3& trans)
|
||||
{
|
||||
return mat4(vec4(1.0f, 0.0f, 0.0f, trans.x),
|
||||
vec4(0.0f, 1.0f, 0.0f, trans.y),
|
||||
vec4(0.0f, 0.0f, 1.0f, trans.z),
|
||||
vec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
}
|
||||
|
||||
static mat4 translate(float x, float y, float z)
|
||||
{
|
||||
return mat4(vec4(1.0f, 0.0f, 0.0f, x),
|
||||
vec4(0.0f, 1.0f, 0.0f, y),
|
||||
vec4(0.0f, 0.0f, 1.0f, z),
|
||||
vec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
}
|
||||
|
||||
static mat4 scale(const vec3& scale)
|
||||
{
|
||||
return mat4(vec4(scale.x, 0.0f, 0.0f, 0.0f),
|
||||
vec4(0.0f, scale.y, 0.0f, 0.0f),
|
||||
vec4(0.0f, 0.0f, scale.z, 0.0f),
|
||||
vec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
}
|
||||
|
||||
static mat4 scale(float x, float y, float z)
|
||||
{
|
||||
return mat4(vec4(x, 0.0f, 0.0f, 0.0f),
|
||||
vec4(0.0f, y, 0.0f, 0.0f),
|
||||
vec4(0.0f, 0.0f, z, 0.0f),
|
||||
vec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
}
|
||||
|
||||
static mat4 frustum(float left, float right, float bottom, float top,
|
||||
float zNear, float zFar)
|
||||
{
|
||||
return mat4(
|
||||
vec4(2.0f * zNear / (right - left), 0.0f,
|
||||
(right + left) / (right - left), 0.0f),
|
||||
vec4(0.0f, 2.0f * zNear / (top - bottom),
|
||||
(top + bottom) / (top - bottom), 0.0f),
|
||||
vec4(0.0f, 0.0f, (zFar + zNear) / (zNear - zFar),
|
||||
2.0f * zFar * zNear / (zNear - zFar)),
|
||||
vec4(0.0f, 0.0f, -1.0f, 0.0f)
|
||||
);
|
||||
}
|
||||
|
||||
static mat4 ortho(float left, float right, float bottom, float top,
|
||||
float zNear, float zFar)
|
||||
{
|
||||
return mat4(
|
||||
vec4(2.0f / (right - left), 0.0f, 0.0f,
|
||||
(right + left) / (left - right)),
|
||||
vec4(0.0f, 2.0f / (top - bottom), 0.0f,
|
||||
(top + bottom) / (bottom - top)),
|
||||
vec4(0.0f, 0.0f, 2.0f / (zNear - zFar),
|
||||
(zFar + zNear) / (zNear - zFar)),
|
||||
vec4(0.0f, 0.0f, 0.0f, 1.0f)
|
||||
);
|
||||
}
|
||||
|
||||
static mat4 lookAt(const vec3& eye, const vec3& center, const vec3& up)
|
||||
{
|
||||
vec3 const forward(vec3::normalize(center - eye));
|
||||
vec3 const side(vec3::normalize(vec3::cross(forward, up)));
|
||||
vec3 const u(vec3::cross(side, forward));
|
||||
|
||||
mat4 result;
|
||||
result[0][0] = side.x;
|
||||
result[1][0] = side.y;
|
||||
result[2][0] = side.z;
|
||||
result[0][1] = u.x;
|
||||
result[1][1] = u.y;
|
||||
result[2][1] = u.z;
|
||||
result[0][2] =-forward.x;
|
||||
result[1][2] =-forward.y;
|
||||
result[2][2] =-forward.z;
|
||||
result[3][0] =-vec3::dot(side, eye);
|
||||
result[3][1] =-vec3::dot(u, eye);
|
||||
result[3][2] = vec3::dot(forward, eye);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static mat4 lookAt(float eyeX, float eyeY, float eyeZ,
|
||||
float centerX, float centerY, float centerZ,
|
||||
float upX, float upY, float upZ)
|
||||
{
|
||||
return mat4::lookAt(vec3(eyeX, eyeY, eyeZ),
|
||||
vec3(centerX, centerY, centerZ),
|
||||
vec3(upX, upY, upZ));
|
||||
}
|
||||
|
||||
static mat4 perspective(float fovY, float aspect, float zNear, float zFar)
|
||||
{
|
||||
float scaleY = 1.0f / tan(fovY * 3.1415962f / 360.0f);
|
||||
return mat4(
|
||||
vec4(scaleY / aspect, 0.0f, 0.0f, 0.0f),
|
||||
vec4(0.0f, scaleY, 0.0f, 0.0f),
|
||||
vec4(0.0f, 0.0f, (zFar + zNear) / (zNear - zFar),
|
||||
(2.0f * zFar * zNear) / (zNear - zFar)),
|
||||
vec4(0.0f, 0.0f, -1.0f, 0.0f)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* VECMATH_9B7E1CFE-346D-11E6-AFA2-D7DC87495A69_H */
|
||||
@@ -0,0 +1,80 @@
|
||||
# Copyright 2020 Andreas Atteneder
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
function(compile_shader shader_target shader_name shader_src_path shader_path)
|
||||
|
||||
set(vert_name "${shader_name}.vert")
|
||||
set(vert2spirv_in "${shader_src_path}/${vert_name}")
|
||||
set(vert2spirv_out "${CMAKE_CURRENT_BINARY_DIR}/${shader_path}/${vert_name}.spv")
|
||||
|
||||
add_custom_command(OUTPUT
|
||||
${vert2spirv_out}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${shader_path}
|
||||
COMMAND glslc "-fshader-stage=vertex" -o "${vert2spirv_out}" "${vert2spirv_in}"
|
||||
DEPENDS ${vert2spirv_in}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
||||
COMMENT "Compiling ${vert_name}."
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
set(frag_name "${shader_name}.frag")
|
||||
set(frag2spirv_in "${shader_src_path}/${frag_name}")
|
||||
set(frag2spirv_out "${CMAKE_CURRENT_BINARY_DIR}/${shader_path}/${frag_name}.spv")
|
||||
|
||||
add_custom_command(OUTPUT
|
||||
${frag2spirv_out}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${shader_path}
|
||||
COMMAND glslc "-fshader-stage=fragment" -o "${frag2spirv_out}" "${frag2spirv_in}"
|
||||
DEPENDS ${frag2spirv_in}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
||||
COMMENT "Compiling ${frag_name}."
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
${shader_target}
|
||||
DEPENDS
|
||||
${vert2spirv_out}
|
||||
${frag2spirv_out}
|
||||
SOURCES
|
||||
${vert2spirv_in}
|
||||
${frag2spirv_in}
|
||||
)
|
||||
|
||||
set_target_properties(${shader_target} PROPERTIES EXCLUDE_FROM_ALL "FALSE")
|
||||
|
||||
set(SHADER_SOURCES ${SHADER_SOURCES} ${frag2spirv_out} ${vert2spirv_out} PARENT_SCOPE)
|
||||
|
||||
endfunction(compile_shader)
|
||||
|
||||
function(compile_shader_list shader_target shader_src_path shader_path)
|
||||
|
||||
foreach(shader ${ARGN})
|
||||
set(spirv_in "${shader_src_path}/${shader}")
|
||||
set(spirv_out "${CMAKE_CURRENT_BINARY_DIR}/${shader_path}/${shader}.spv")
|
||||
|
||||
add_custom_command(OUTPUT
|
||||
${spirv_out}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${shader_path}
|
||||
COMMAND glslc -o "${spirv_out}" "${spirv_in}"
|
||||
DEPENDS ${spirv_in}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
||||
COMMENT "Compiling ${shader}."
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
list(APPEND inputs ${spirv_in})
|
||||
list(APPEND outputs ${spirv_out})
|
||||
endforeach()
|
||||
|
||||
add_custom_target(
|
||||
${shader_target}
|
||||
DEPENDS ${outputs}
|
||||
SOURCES ${inputs}
|
||||
)
|
||||
|
||||
set_target_properties(${shader_target} PROPERTIES EXCLUDE_FROM_ALL "FALSE")
|
||||
|
||||
set(SHADER_SOURCES ${SHADER_SOURCES} ${outputs} PARENT_SCOPE)
|
||||
|
||||
endfunction()
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2016-2020 Mark Callow
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
static const float cube_face[] =
|
||||
{
|
||||
-1.0f, +1.0f, +1.0f, /* Front */
|
||||
+1.0f, -1.0f, +1.0f,
|
||||
+1.0f, +1.0f, +1.0f,
|
||||
-1.0f, -1.0f, +1.0f,
|
||||
-1.0f, +1.0f, -1.0f, /* Back */
|
||||
+1.0f, -1.0f, -1.0f,
|
||||
+1.0f, +1.0f, -1.0f,
|
||||
-1.0f, -1.0f, -1.0f,
|
||||
+1.0f, -1.0f, +1.0f, /* Right */
|
||||
+1.0f, +1.0f, -1.0f,
|
||||
+1.0f, +1.0f, +1.0f,
|
||||
+1.0f, -1.0f, -1.0f,
|
||||
-1.0f, -1.0f, +1.0f, /* Left */
|
||||
-1.0f, +1.0f, -1.0f,
|
||||
-1.0f, +1.0f, +1.0f,
|
||||
-1.0f, -1.0f, -1.0f,
|
||||
-1.0f, -1.0f, +1.0f, /* Bottom */
|
||||
+1.0f, -1.0f, -1.0f,
|
||||
+1.0f, -1.0f, +1.0f,
|
||||
-1.0f, -1.0f, -1.0f,
|
||||
-1.0f, +1.0f, +1.0f, /* Top */
|
||||
+1.0f, +1.0f, -1.0f,
|
||||
+1.0f, +1.0f, +1.0f,
|
||||
-1.0f, +1.0f, -1.0f,
|
||||
};
|
||||
#define CUBE_NUM_FACE_COMPONENTS 3
|
||||
#define CUBE_FACE_STRIDE (sizeof(float) * CUBE_NUM_FACE_COMPONENTS)
|
||||
|
||||
static const float cube_color[] = /* almost random colors */
|
||||
{
|
||||
0.7f, 0.1f, 0.2f, 0.0f, 0.8f, 0.9f, 0.3f, 0.0f, 0.4f, 1.0f, 0.5f, 0.0f, 0.0f, 0.6f, 0.1f, 0.0f,
|
||||
0.8f, 0.2f, 0.3f, 0.0f, 0.9f, 1.0f, 0.4f, 0.0f, 0.5f, 0.0f, 0.6f, 0.0f, 0.1f, 0.7f, 0.2f, 0.0f,
|
||||
0.9f, 0.3f, 0.4f, 0.0f, 1.0f, 0.0f, 0.5f, 0.0f, 0.6f, 0.1f, 0.7f, 0.0f, 0.2f, 0.8f, 0.3f, 0.0f,
|
||||
1.0f, 0.4f, 0.5f, 0.0f, 0.0f, 0.1f, 0.6f, 0.0f, 0.7f, 0.2f, 0.8f, 0.0f, 0.3f, 0.9f, 0.4f, 0.0f,
|
||||
0.0f, 0.5f, 0.6f, 0.0f, 0.1f, 0.2f, 0.7f, 0.0f, 0.8f, 0.3f, 0.9f, 0.0f, 0.4f, 1.0f, 0.5f, 0.0f,
|
||||
0.1f, 0.6f, 0.7f, 0.0f, 0.2f, 0.3f, 0.8f, 0.0f, 0.9f, 0.4f, 1.0f, 0.0f, 0.5f, 0.0f, 0.6f, 0.0f,
|
||||
};
|
||||
static const float cube_texture[] =
|
||||
{
|
||||
0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
|
||||
1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
|
||||
0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
|
||||
1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
|
||||
0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
|
||||
1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f
|
||||
};
|
||||
static const float cube_normal[] =
|
||||
{
|
||||
0.0f, 0.0f, +1.0f, 0.0f, 0.0f, +1.0f, 0.0f, 0.0f, +1.0f, 0.0f, 0.0f, +1.0f,
|
||||
0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f,
|
||||
+1.0f, 0.0f, 0.0f, +1.0f, 0.0f, 0.0f, +1.0f, 0.0f, 0.0f, +1.0f, 0.0f, 0.0f,
|
||||
-1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
|
||||
0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f,
|
||||
0.0f, +1.0f, 0.0f, 0.0f, +1.0f, 0.0f, 0.0f, +1.0f, 0.0f, 0.0f, +1.0f, 0.0f,
|
||||
};
|
||||
|
||||
static const unsigned short cube_index_buffer[] = {
|
||||
0, 3, 1, 2, 0, 1, /* Front */
|
||||
6, 5, 4, 5, 7, 4, /* Back */
|
||||
8,11, 9,10, 8, 9, /* Right */
|
||||
15,12,13,12,14,13, /* Left */
|
||||
16,19,17,18,16,17, /* Bottom */
|
||||
23,20,21,20,22,21 /* Top */
|
||||
};
|
||||
#define CUBE_NUM_INDICES (sizeof(cube_index_buffer) / sizeof(unsigned short))
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Vulkan Samples
|
||||
*
|
||||
* Copyright 2015-2016 Valve Corporation
|
||||
* Copyright 2015-2016 LunarG, Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Mesh and VertexFormat Data
|
||||
//--------------------------------------------------------------------------------------
|
||||
struct Vertex {
|
||||
float posX, posY, posZ, posW; // Position data
|
||||
float r, g, b, a; // Color
|
||||
};
|
||||
|
||||
struct VertexUV {
|
||||
float posX, posY, posZ, posW; // Position data
|
||||
float u, v; // texture u,v
|
||||
};
|
||||
|
||||
#define XYZ1(_x_, _y_, _z_) (_x_), (_y_), (_z_), 1.f
|
||||
#define UV(_u_, _v_) (_u_), (_v_)
|
||||
|
||||
static const Vertex g_vbData[] = {
|
||||
{XYZ1(-1, -1, -1), XYZ1(0.f, 0.f, 0.f)},
|
||||
{XYZ1(1, -1, -1), XYZ1(1.f, 0.f, 0.f)},
|
||||
{XYZ1(-1, 1, -1), XYZ1(0.f, 1.f, 0.f)},
|
||||
{XYZ1(-1, 1, -1), XYZ1(0.f, 1.f, 0.f)},
|
||||
{XYZ1(1, -1, -1), XYZ1(1.f, 0.f, 0.f)},
|
||||
{XYZ1(1, 1, -1), XYZ1(1.f, 1.f, 0.f)},
|
||||
|
||||
{XYZ1(-1, -1, 1), XYZ1(0.f, 0.f, 1.f)},
|
||||
{XYZ1(-1, 1, 1), XYZ1(0.f, 1.f, 1.f)},
|
||||
{XYZ1(1, -1, 1), XYZ1(1.f, 0.f, 1.f)},
|
||||
{XYZ1(1, -1, 1), XYZ1(1.f, 0.f, 1.f)},
|
||||
{XYZ1(-1, 1, 1), XYZ1(0.f, 1.f, 1.f)},
|
||||
{XYZ1(1, 1, 1), XYZ1(1.f, 1.f, 1.f)},
|
||||
|
||||
{XYZ1(1, 1, 1), XYZ1(1.f, 1.f, 1.f)},
|
||||
{XYZ1(1, 1, -1), XYZ1(1.f, 1.f, 0.f)},
|
||||
{XYZ1(1, -1, 1), XYZ1(1.f, 0.f, 1.f)},
|
||||
{XYZ1(1, -1, 1), XYZ1(1.f, 0.f, 1.f)},
|
||||
{XYZ1(1, 1, -1), XYZ1(1.f, 1.f, 0.f)},
|
||||
{XYZ1(1, -1, -1), XYZ1(1.f, 0.f, 0.f)},
|
||||
|
||||
{XYZ1(-1, 1, 1), XYZ1(0.f, 1.f, 1.f)},
|
||||
{XYZ1(-1, -1, 1), XYZ1(0.f, 0.f, 1.f)},
|
||||
{XYZ1(-1, 1, -1), XYZ1(0.f, 1.f, 0.f)},
|
||||
{XYZ1(-1, 1, -1), XYZ1(0.f, 1.f, 0.f)},
|
||||
{XYZ1(-1, -1, 1), XYZ1(0.f, 0.f, 1.f)},
|
||||
{XYZ1(-1, -1, -1), XYZ1(0.f, 0.f, 0.f)},
|
||||
|
||||
{XYZ1(1, 1, 1), XYZ1(1.f, 1.f, 1.f)},
|
||||
{XYZ1(-1, 1, 1), XYZ1(0.f, 1.f, 1.f)},
|
||||
{XYZ1(1, 1, -1), XYZ1(1.f, 1.f, 0.f)},
|
||||
{XYZ1(1, 1, -1), XYZ1(1.f, 1.f, 0.f)},
|
||||
{XYZ1(-1, 1, 1), XYZ1(0.f, 1.f, 1.f)},
|
||||
{XYZ1(-1, 1, -1), XYZ1(0.f, 1.f, 0.f)},
|
||||
|
||||
{XYZ1(1, -1, 1), XYZ1(1.f, 0.f, 1.f)},
|
||||
{XYZ1(1, -1, -1), XYZ1(1.f, 0.f, 0.f)},
|
||||
{XYZ1(-1, -1, 1), XYZ1(0.f, 0.f, 1.f)},
|
||||
{XYZ1(-1, -1, 1), XYZ1(0.f, 0.f, 1.f)},
|
||||
{XYZ1(1, -1, -1), XYZ1(1.f, 0.f, 0.f)},
|
||||
{XYZ1(-1, -1, -1), XYZ1(0.f, 0.f, 0.f)},
|
||||
};
|
||||
|
||||
static const Vertex g_vb_solid_face_colors_Data[] = {
|
||||
{XYZ1(-1, -1, -1), XYZ1(1.f, 0.f, 0.f)},
|
||||
{XYZ1(1, -1, -1), XYZ1(1.f, 0.f, 0.f)},
|
||||
{XYZ1(-1, 1, -1), XYZ1(1.f, 0.f, 0.f)},
|
||||
{XYZ1(-1, 1, -1), XYZ1(1.f, 0.f, 0.f)},
|
||||
{XYZ1(1, -1, -1), XYZ1(1.f, 0.f, 0.f)},
|
||||
{XYZ1(1, 1, -1), XYZ1(1.f, 0.f, 0.f)},
|
||||
|
||||
{XYZ1(-1, -1, 1), XYZ1(0.f, 1.f, 0.f)},
|
||||
{XYZ1(-1, 1, 1), XYZ1(0.f, 1.f, 0.f)},
|
||||
{XYZ1(1, -1, 1), XYZ1(0.f, 1.f, 0.f)},
|
||||
{XYZ1(1, -1, 1), XYZ1(0.f, 1.f, 0.f)},
|
||||
{XYZ1(-1, 1, 1), XYZ1(0.f, 1.f, 0.f)},
|
||||
{XYZ1(1, 1, 1), XYZ1(0.f, 1.f, 0.f)},
|
||||
|
||||
{XYZ1(1, 1, 1), XYZ1(0.f, 0.f, 1.f)},
|
||||
{XYZ1(1, 1, -1), XYZ1(0.f, 0.f, 1.f)},
|
||||
{XYZ1(1, -1, 1), XYZ1(0.f, 0.f, 1.f)},
|
||||
{XYZ1(1, -1, 1), XYZ1(0.f, 0.f, 1.f)},
|
||||
{XYZ1(1, 1, -1), XYZ1(0.f, 0.f, 1.f)},
|
||||
{XYZ1(1, -1, -1), XYZ1(0.f, 0.f, 1.f)},
|
||||
|
||||
{XYZ1(-1, 1, 1), XYZ1(1.f, 1.f, 0.f)},
|
||||
{XYZ1(-1, -1, 1), XYZ1(1.f, 1.f, 0.f)},
|
||||
{XYZ1(-1, 1, -1), XYZ1(1.f, 1.f, 0.f)},
|
||||
{XYZ1(-1, 1, -1), XYZ1(1.f, 1.f, 0.f)},
|
||||
{XYZ1(-1, -1, 1), XYZ1(1.f, 1.f, 0.f)},
|
||||
{XYZ1(-1, -1, -1), XYZ1(1.f, 1.f, 0.f)},
|
||||
|
||||
{XYZ1(1, 1, 1), XYZ1(1.f, 0.f, 1.f)},
|
||||
{XYZ1(-1, 1, 1), XYZ1(1.f, 0.f, 1.f)},
|
||||
{XYZ1(1, 1, -1), XYZ1(1.f, 0.f, 1.f)},
|
||||
{XYZ1(1, 1, -1), XYZ1(1.f, 0.f, 1.f)},
|
||||
{XYZ1(-1, 1, 1), XYZ1(1.f, 0.f, 1.f)},
|
||||
{XYZ1(-1, 1, -1), XYZ1(1.f, 0.f, 1.f)},
|
||||
|
||||
{XYZ1(1, -1, 1), XYZ1(0.f, 1.f, 1.f)},
|
||||
{XYZ1(1, -1, -1), XYZ1(0.f, 1.f, 1.f)},
|
||||
{XYZ1(-1, -1, 1), XYZ1(0.f, 1.f, 1.f)},
|
||||
{XYZ1(-1, -1, 1), XYZ1(0.f, 1.f, 1.f)},
|
||||
{XYZ1(1, -1, -1), XYZ1(0.f, 1.f, 1.f)},
|
||||
{XYZ1(-1, -1, -1), XYZ1(0.f, 1.f, 1.f)},
|
||||
};
|
||||
|
||||
static const VertexUV g_vb_texture_Data[] = {
|
||||
{XYZ1(-1, -1, -1), UV(0.f, 0.f)},
|
||||
{XYZ1(-1, 1, 1), UV(1.f, 1.f)},
|
||||
{XYZ1(-1, -1, 1), UV(1.f, 0.f)},
|
||||
{XYZ1(-1, 1, 1), UV(1.f, 1.f)},
|
||||
{XYZ1(-1, -1, -1), UV(0.f, 0.f)},
|
||||
{XYZ1(-1, 1, -1), UV(0.f, 1.f)},
|
||||
|
||||
{XYZ1(-1, -1, -1), UV(1.f, 0.f)},
|
||||
{XYZ1(1, -1, -1), UV(0.f, 0.f)},
|
||||
{XYZ1(1, 1, -1), UV(0.f, 1.f)},
|
||||
{XYZ1(-1, -1, -1), UV(1.f, 0.f)},
|
||||
{XYZ1(1, 1, -1), UV(0.f, 1.f)},
|
||||
{XYZ1(-1, 1, -1), UV(1.f, 1.f)},
|
||||
|
||||
{XYZ1(-1, -1, -1), UV(1.f, 1.f)},
|
||||
{XYZ1(1, -1, 1), UV(0.f, 0.f)},
|
||||
{XYZ1(1, -1, -1), UV(1.f, 0.f)},
|
||||
{XYZ1(-1, -1, -1), UV(1.f, 1.f)},
|
||||
{XYZ1(-1, -1, 1), UV(0.f, 1.f)},
|
||||
{XYZ1(1, -1, 1), UV(0.f, 0.f)},
|
||||
|
||||
{XYZ1(-1, 1, -1), UV(1.f, 1.f)},
|
||||
{XYZ1(1, 1, 1), UV(0.f, 0.f)},
|
||||
{XYZ1(-1, 1, 1), UV(0.f, 1.f)},
|
||||
{XYZ1(-1, 1, -1), UV(1.f, 1.f)},
|
||||
{XYZ1(1, 1, -1), UV(1.f, 0.f)},
|
||||
{XYZ1(1, 1, 1), UV(0.f, 0.f)},
|
||||
|
||||
{XYZ1(1, 1, -1), UV(1.f, 1.f)},
|
||||
{XYZ1(1, -1, 1), UV(0.f, 0.f)},
|
||||
{XYZ1(1, 1, 1), UV(0.f, 1.f)},
|
||||
{XYZ1(1, -1, 1), UV(0.f, 0.f)},
|
||||
{XYZ1(1, 1, -1), UV(1.f, 1.f)},
|
||||
{XYZ1(1, -1, -1), UV(1.f, 0.f)},
|
||||
|
||||
{XYZ1(-1, 1, 1), UV(0.f, 1.f)},
|
||||
{XYZ1(1, 1, 1), UV(1.f, 1.f)},
|
||||
{XYZ1(-1, -1, 1), UV(0.f, 0.f)},
|
||||
{XYZ1(-1, -1, 1), UV(0.f, 0.f)},
|
||||
{XYZ1(1, 1, 1), UV(1.f, 1.f)},
|
||||
{XYZ1(1, -1, 1), UV(1.f, 0.f)},
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2016-2020 Mark Callow
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Frame definition
|
||||
*/
|
||||
static const GLbyte frame_position[] =
|
||||
{
|
||||
-1, -1, 0,
|
||||
1, -1, 0,
|
||||
1, 1, 0,
|
||||
-1, 1, 0
|
||||
};
|
||||
|
||||
static const GLbyte frame_color[] =
|
||||
{
|
||||
1, 1, 0,
|
||||
1, 1, 0,
|
||||
1, 1, 0,
|
||||
1, 1, 0
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set et sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2016-2020 Mark Callow
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Quad definition
|
||||
*/
|
||||
static const GLfloat quad_position[] =
|
||||
{
|
||||
1.0f, -1.0f, 0,
|
||||
1.0f, 1.0f, 0,
|
||||
-1.0f, -1.0f, 0,
|
||||
-1.0f, 1.0f, 0
|
||||
};
|
||||
|
||||
static const GLfloat quad_color[] =
|
||||
{
|
||||
0.7f, 0.1f, 0.2f,
|
||||
0.8f, 0.9f, 0.3f,
|
||||
0.4f, 1.0f, 0.5f,
|
||||
0.0f, 0.6f, 0.1f
|
||||
};
|
||||
|
||||
static GLfloat quad_texture[] =
|
||||
{
|
||||
1.0f, 0.0f,
|
||||
1.0f, 1.0f,
|
||||
0.0f, 0.0f,
|
||||
0.0f, 1.0f
|
||||
};
|
||||
@@ -0,0 +1,449 @@
|
||||
# Copyright 2020 Andreas Atteneder
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set(OPENGL_ES_EMULATOR "" CACHE PATH "Path to OpenGL ES emulation libraries")
|
||||
|
||||
if(NOT APPLE_LOCKED_OS)
|
||||
find_package(OpenGL REQUIRED)
|
||||
endif()
|
||||
if(WIN32)
|
||||
find_package(GLEW REQUIRED)
|
||||
endif()
|
||||
|
||||
function( create_gl_target target version sources common_resources test_images
|
||||
KTX_GL_CONTEXT_PROFILE
|
||||
KTX_GL_CONTEXT_MAJOR_VERSION KTX_GL_CONTEXT_MINOR_VERSION
|
||||
EMULATE_GLES)
|
||||
|
||||
set( resources ${common_resources};${test_images} )
|
||||
|
||||
add_executable( ${target}
|
||||
${EXE_FLAG}
|
||||
glloadtests.cmake
|
||||
${sources}
|
||||
${resources}
|
||||
)
|
||||
|
||||
set_code_sign(${target})
|
||||
|
||||
# Nota Bene.
|
||||
#
|
||||
# 1. With the Visual Studio generator, at least, The SDL and GLEW
|
||||
# includes coming from GLAppSDL are being converted to system
|
||||
# includes. To see them in VS, view the whole command line in
|
||||
# the compile section of the project properties and look at the
|
||||
# Additional Options pane at the bottom.
|
||||
# 2. GL_APP_SDL's INTERFACE_INCLUDE_DIRECTORIES includes the SYSTEM
|
||||
# include from appfwSDL.
|
||||
#
|
||||
# I do not understand the reasons for either of these.
|
||||
target_include_directories(
|
||||
${target}
|
||||
PRIVATE
|
||||
$<TARGET_PROPERTY:GLAppSDL,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
$<TARGET_PROPERTY:ktx,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
$<TARGET_PROPERTY:objUtil,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
)
|
||||
|
||||
set_target_properties(${target} PROPERTIES
|
||||
CXX_VISIBILITY_PRESET ${STATIC_APP_LIB_SYMBOL_VISIBILITY}
|
||||
)
|
||||
|
||||
|
||||
target_link_libraries(
|
||||
${target}
|
||||
objUtil
|
||||
GLAppSDL
|
||||
appfwSDL
|
||||
ktx
|
||||
)
|
||||
|
||||
if(NOT EMSCRIPTEN AND NOT EMULATE_GLES)
|
||||
target_link_libraries(
|
||||
${target}
|
||||
${OPENGL_LIBRARIES}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
if(IOS)
|
||||
# This is location where CMake puts the configured Info.plist.
|
||||
# I have not found a CMake variable for this.
|
||||
set( launch_screen ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${target}.dir/LaunchScreen.storyboard )
|
||||
configure_file( glloadtests/resources/ios/LaunchScreen.storyboard.in
|
||||
${launch_screen}
|
||||
)
|
||||
set( INFO_PLIST_IN "${PROJECT_SOURCE_DIR}/tests/loadtests/glloadtests/resources/ios/Info.plist.in" )
|
||||
set( icon_launch_assets
|
||||
${PROJECT_SOURCE_DIR}/icons/ios/CommonIcons.xcassets
|
||||
glloadtests/resources/ios/LaunchImages.xcassets
|
||||
${launch_screen}
|
||||
)
|
||||
target_sources( ${target}
|
||||
PRIVATE
|
||||
${PROJECT_SOURCE_DIR}/icons/ios/CommonIcons.xcassets
|
||||
glloadtests/resources/ios/LaunchImages.xcassets
|
||||
glloadtests/resources/ios/LaunchScreen.storyboard.in
|
||||
)
|
||||
# Add to resources so they'll be copied to the bundle.
|
||||
list( APPEND resources ${icon_launch_assets} )
|
||||
target_link_libraries(
|
||||
${target}
|
||||
${AudioToolbox_LIBRARY}
|
||||
${AVFoundation_LIBRARY}
|
||||
${CoreAudio_LIBRARY}
|
||||
${CoreBluetooth_LIBRARY}
|
||||
${CoreGraphics_LIBRARY}
|
||||
${CoreMotion_LIBRARY}
|
||||
${CoreHaptics_LIBRARY}
|
||||
${Foundation_LIBRARY}
|
||||
${GameController_LIBRARY}
|
||||
${Metal_LIBRARY}
|
||||
${OpenGLES_LIBRARY}
|
||||
${QuartzCore_LIBRARY}
|
||||
${UIKit_LIBRARY}
|
||||
)
|
||||
else()
|
||||
set( INFO_PLIST_IN "${PROJECT_SOURCE_DIR}/tests/loadtests/glloadtests/resources/mac/Info.plist.in" )
|
||||
endif()
|
||||
elseif(EMSCRIPTEN)
|
||||
# Beware of de-duplication in list expansion for commands and options.
|
||||
# SHELL: prevents it but if they are separate items in the list they
|
||||
# be de-duplicated.
|
||||
list( TRANSFORM test_images REPLACE
|
||||
"(${PROJECT_SOURCE_DIR}/tests/testimages/([a-zA-Z0-9_].*$))"
|
||||
"SHELL:--preload-file \\1@\\2"
|
||||
OUTPUT_VARIABLE preloads
|
||||
)
|
||||
target_link_options(
|
||||
${target}
|
||||
PRIVATE
|
||||
"SHELL:--source-map-base ./"
|
||||
${preloads}
|
||||
"SHELL:--exclude-file '${PROJECT_SOURCE_DIR}/tests/testimages/cubemap*'"
|
||||
"SHELL:--use-port=sdl3"
|
||||
"SHELL:-s ALLOW_MEMORY_GROWTH=1"
|
||||
"SHELL:-s DISABLE_EXCEPTION_CATCHING=0"
|
||||
"SHELL:-s USE_WEBGL2=1"
|
||||
)
|
||||
elseif(WIN32)
|
||||
target_sources(
|
||||
${target}
|
||||
PRIVATE
|
||||
glloadtests/resources/win/glloadtests.rc
|
||||
glloadtests/resources/win/resource.h
|
||||
)
|
||||
if(EMULATE_GLES)
|
||||
if (KTX_GL_CONTEXT_MAJOR_VERSION EQUAL 1)
|
||||
target_link_libraries(
|
||||
${target}
|
||||
"${OPENGL_ES_EMULATOR}/libGLES_CM.lib"
|
||||
"${OPENGL_ES_EMULATOR}/libEGL.lib"
|
||||
)
|
||||
else()
|
||||
target_link_libraries(
|
||||
${target}
|
||||
"${OPENGL_ES_EMULATOR}/libGLESv2.lib"
|
||||
"${OPENGL_ES_EMULATOR}/libEGL.lib"
|
||||
)
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(
|
||||
${target}
|
||||
${GLEW_LIBRARIES}
|
||||
)
|
||||
endif()
|
||||
ensure_runtime_dependencies_windows(${target})
|
||||
elseif(LINUX)
|
||||
# The output file is configured at CMake config time.
|
||||
configure_file(glloadtests/resources/linux/glloadtests.desktop.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${target}.desktop
|
||||
)
|
||||
target_sources(
|
||||
${target}
|
||||
PRIVATE
|
||||
# Put the input file in sources as that is what must be edited.
|
||||
glloadtests/resources/linux/glloadtests.desktop.in
|
||||
#${CMAKE_CURRENT_BINARY_DIR}/${target}.desktop
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries( ${target} ${LOAD_TEST_COMMON_LIBS} )
|
||||
|
||||
if(NOT EMULATE_GLES OR KTX_GL_CONTEXT_MAJOR_VERSION GREATER 1)
|
||||
target_compile_definitions(
|
||||
${target}
|
||||
PRIVATE
|
||||
$<TARGET_PROPERTY:ktx,INTERFACE_COMPILE_DEFINITIONS>
|
||||
GL_CONTEXT_PROFILE=${KTX_GL_CONTEXT_PROFILE}
|
||||
GL_CONTEXT_MAJOR_VERSION=${KTX_GL_CONTEXT_MAJOR_VERSION}
|
||||
GL_CONTEXT_MINOR_VERSION=${KTX_GL_CONTEXT_MINOR_VERSION}
|
||||
$<$<PLATFORM_ID:Windows>:NOMINMAX>
|
||||
)
|
||||
else()
|
||||
target_compile_definitions(
|
||||
${target}
|
||||
PRIVATE
|
||||
$<TARGET_PROPERTY:ktx,INTERFACE_COMPILE_DEFINITIONS>
|
||||
$<$<PLATFORM_ID:Windows>:NOMINMAX>
|
||||
)
|
||||
endif()
|
||||
|
||||
set_target_properties( ${target} PROPERTIES RESOURCE "${resources}" )
|
||||
|
||||
if(APPLE)
|
||||
set(product_name "${target}")
|
||||
set( bundle_identifier org.khronos.ktx.${product_name} )
|
||||
# This property must be set to avoid an Xcode warning.
|
||||
set_target_properties(${target} PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.khronos.ktx.${product_name}")
|
||||
# See important comment about MACOSX_BUNDLE_INFO_PLIST and these
|
||||
# properties in ./vkloadtests.cmake.
|
||||
set_target_properties( ${target} PROPERTIES
|
||||
MACOSX_BUNDLE_BUNDLE_NAME ${product_name}
|
||||
MACOSX_BUNDLE_EXECUTABLE_NAME ${product_name}
|
||||
MACOSX_BUNDLE_COPYRIGHT "© 2024 Khronos Group, Inc."
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER ${bundle_identifier}
|
||||
MACOSX_BUNDLE_INFO_PLIST ${INFO_PLIST_IN}
|
||||
MACOSX_BUNDLE_INFO_STRING "View KTX textures; display via OpenGL."
|
||||
MACOSX_BUNDLE_ICON_FILE ${KTX_APP_ICON}
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
|
||||
# Because libassimp is built with bitcode disabled. It's not
|
||||
# important unless submitting to the App Store and currently
|
||||
# bitcode is optional.
|
||||
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
|
||||
XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH "YES"
|
||||
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME ${KTX_APP_ICON_BASENAME}
|
||||
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2" # iPhone and iPad
|
||||
)
|
||||
unset(product_name)
|
||||
unset(bundle_identifier)
|
||||
|
||||
# The generated project code for building an Apple bundle automatically
|
||||
# copies the executable and all files with the RESOURCE property to the
|
||||
# bundle adjusting for the difference in bundle layout between iOS &
|
||||
# macOS.
|
||||
|
||||
if(NOT IOS)
|
||||
set_target_properties( ${target} PROPERTIES
|
||||
INSTALL_RPATH "@executable_path/../Frameworks"
|
||||
)
|
||||
|
||||
if(BUILD_SHARED_LIBS)
|
||||
add_custom_command( TARGET ${target} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:ktx> "$<TARGET_BUNDLE_CONTENT_DIR:${target}>/Frameworks/$<TARGET_FILE_NAME:ktx>"
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink $<TARGET_FILE_NAME:ktx> "$<TARGET_BUNDLE_CONTENT_DIR:${target}>/Frameworks/$<TARGET_SONAME_FILE_NAME:ktx>"
|
||||
COMMENT "Copy KTX library to build destination"
|
||||
)
|
||||
endif()
|
||||
# Re. SDL3 & assimp: no copy required.: vcpkg libs are static or else
|
||||
# vcpkg arranges copy. Brew libs cannot be bundled.
|
||||
|
||||
# Specify destination for cmake --install.
|
||||
install(TARGETS ${target}
|
||||
BUNDLE
|
||||
DESTINATION /Applications
|
||||
COMPONENT GlLoadTestApps
|
||||
)
|
||||
|
||||
endif()
|
||||
|
||||
else()
|
||||
if(EMSCRIPTEN)
|
||||
set_target_properties(${target} PROPERTIES SUFFIX ".html")
|
||||
endif()
|
||||
|
||||
# This copies the resources next to the executable for ease
|
||||
# of use during debugging and testing.
|
||||
add_custom_command( TARGET ${target} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||
$<TARGET_FILE_DIR:${target}>/../resources
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${resources}
|
||||
$<TARGET_FILE_DIR:${target}>/../resources
|
||||
)
|
||||
|
||||
# See important comment and TODO:s starting at line 365
|
||||
# in ./vkloadtests.cmake regarding installation of these
|
||||
# targets. Search for "keep the resources".
|
||||
|
||||
set_target_properties( ${target} PROPERTIES
|
||||
INSTALL_RPATH "\$ORIGIN;${CMAKE_INSTALL_FULL_LIBDIR}"
|
||||
)
|
||||
|
||||
######### IMPORTANT ######
|
||||
# When installing via `cmake --install` ALSO install the
|
||||
# library component. There seems no way to make a dependency.
|
||||
##########################
|
||||
# set( destroot "${LOAD_TEST_DESTROOT}/$<TARGET_FILE_NAME:${target}>")
|
||||
# NOTE: WHEN RUNNING MANUAL INSTALLS INSTALL library COMPONENT TOO.
|
||||
# install(TARGETS ${target}
|
||||
# RUNTIME
|
||||
# DESTINATION ${destroot}/bin
|
||||
# COMPONENT GlLoadTestApps
|
||||
# LIBRARY
|
||||
# DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
# COMPONENT GlLoadTestApps
|
||||
# RESOURCE
|
||||
# DESTINATION ${destroot}/resources
|
||||
# COMPONENT GlLoadTestApps
|
||||
# )
|
||||
# install(TARGETS ktx
|
||||
# RUNTIME
|
||||
# DESTINATION ${destroot}/bin
|
||||
# COMPONENT GlLoadTestApps
|
||||
# LIBRARY
|
||||
# DESTINATION ${destroot}/lib
|
||||
# COMPONENT GlLoadTestApps
|
||||
# )
|
||||
# if(LINUX)
|
||||
# # Add a link from the regular bin directory to put command
|
||||
# # on PATH.
|
||||
# install(CODE "
|
||||
# EXECUTE_PROCESS(COMMAND ln -s ${destroot}/bin/$<TARGET_FILE_NAME:${target}> ${CMAKE_INSTALL_FULL_BINDIR}
|
||||
# )"
|
||||
# COMPONENT GlLoadTestApps
|
||||
# )
|
||||
# install(FILES
|
||||
# ${CMAKE_CURRENT_BINARY_DIR}/${target}.desktop
|
||||
# DESTINATION /usr/share/applications
|
||||
# COMPONENT GlLoadTestApps
|
||||
# )
|
||||
# endif(LINUX)
|
||||
endif()
|
||||
endfunction( create_gl_target target )
|
||||
|
||||
|
||||
set( ES1_TEST_IMAGES
|
||||
no-npot.ktx
|
||||
hi_mark.ktx
|
||||
luminance-reference-metadata.ktx
|
||||
orient-up-metadata.ktx
|
||||
orient-down-metadata.ktx
|
||||
etc1.ktx
|
||||
etc2-rgb.ktx
|
||||
etc2-rgba1.ktx
|
||||
etc2-rgba8.ktx
|
||||
rgba-reference.ktx
|
||||
rgb-reference.ktx
|
||||
rgb-amg-reference.ktx
|
||||
rgb-mipmap-reference.ktx
|
||||
hi_mark_sq.ktx
|
||||
)
|
||||
list( TRANSFORM ES1_TEST_IMAGES
|
||||
PREPEND "${PROJECT_SOURCE_DIR}/tests/testimages/"
|
||||
)
|
||||
|
||||
set( ES1_SOURCES
|
||||
glloadtests/gles1/ES1LoadTests.cpp
|
||||
glloadtests/gles1/DrawTexture.cpp
|
||||
glloadtests/gles1/DrawTexture.h
|
||||
glloadtests/gles1/TexturedCube.cpp
|
||||
glloadtests/gles1/TexturedCube.h
|
||||
)
|
||||
|
||||
set( GL3_TEST_IMAGES
|
||||
etc1s_Iron_Bars_001_normal.ktx2
|
||||
uastc_Iron_Bars_001_normal.ktx2
|
||||
color_grid_uastc_zstd.ktx2
|
||||
color_grid_zstd.ktx2
|
||||
color_grid_uastc.ktx2
|
||||
color_grid_basis.ktx2
|
||||
kodim17_basis.ktx2
|
||||
kodim17_basis.ktx2
|
||||
FlightHelmet_baseColor_basis.ktx2
|
||||
rgba-reference-u.ktx2
|
||||
rgba-reference-u.ktx2
|
||||
rgba-reference-u.ktx2
|
||||
cubemap_goldengate_uastc_rdo4_zstd5_rd.ktx2
|
||||
cubemap_yokohama_basis_rd.ktx2
|
||||
orient-down-metadata-u.ktx2
|
||||
orient-down-metadata-u.ktx2
|
||||
texturearray_bc3_unorm.ktx2
|
||||
texturearray_astc_8x8_unorm.ktx2
|
||||
texturearray_etc2_unorm.ktx2
|
||||
3dtex_7_reference_u.ktx2
|
||||
rgb-mipmap-reference-u.ktx2
|
||||
hi_mark.ktx
|
||||
orient-up-metadata.ktx
|
||||
orient-down-metadata.ktx
|
||||
not4_rgb888_srgb.ktx
|
||||
etc1.ktx
|
||||
etc2-rgb.ktx
|
||||
etc2-rgba1.ktx
|
||||
etc2-rgba8.ktx
|
||||
etc2-sRGB.ktx
|
||||
etc2-sRGBa1.ktx
|
||||
etc2-sRGBa8.ktx
|
||||
rgba-reference.ktx
|
||||
rgb-reference.ktx
|
||||
conftestimage_R11_EAC.ktx
|
||||
conftestimage_SIGNED_R11_EAC.ktx
|
||||
conftestimage_RG11_EAC.ktx
|
||||
conftestimage_SIGNED_RG11_EAC.ktx
|
||||
texturearray_bc3_unorm.ktx
|
||||
texturearray_astc_8x8_unorm.ktx
|
||||
texturearray_etc2_unorm.ktx
|
||||
rgb-amg-reference.ktx
|
||||
rgb-mipmap-reference.ktx
|
||||
hi_mark_sq.ktx
|
||||
)
|
||||
list( TRANSFORM GL3_TEST_IMAGES
|
||||
PREPEND "${PROJECT_SOURCE_DIR}/tests/testimages/"
|
||||
)
|
||||
set( GL3_RESOURCE_FILES ${LOAD_TEST_COMMON_RESOURCE_FILES} ${GL3_TEST_IMAGES} )
|
||||
|
||||
set( GL3_SOURCES
|
||||
common/TranscodeTargetStrToFmt.cpp
|
||||
common/TranscodeTargetStrToFmt.h
|
||||
common/disable_glm_warnings.h
|
||||
common/reenable_warnings.h
|
||||
glloadtests/shader-based/DrawTexture.cpp
|
||||
glloadtests/shader-based/DrawTexture.h
|
||||
glloadtests/shader-based/EncodeTexture.cpp
|
||||
glloadtests/shader-based/EncodeTexture.h
|
||||
glloadtests/shader-based/GL3LoadTests.cpp
|
||||
glloadtests/shader-based/GL3LoadTestSample.cpp
|
||||
glloadtests/shader-based/GL3LoadTestSample.h
|
||||
glloadtests/shader-based/InstancedSampleBase.cpp
|
||||
glloadtests/shader-based/InstancedSampleBase.h
|
||||
glloadtests/shader-based/mygl.h
|
||||
glloadtests/shader-based/shaders.cpp
|
||||
glloadtests/shader-based/Texture3d.cpp
|
||||
glloadtests/shader-based/Texture3d.h
|
||||
glloadtests/shader-based/TextureArray.cpp
|
||||
glloadtests/shader-based/TextureArray.h
|
||||
glloadtests/shader-based/TextureMipmap.cpp
|
||||
glloadtests/shader-based/TextureMipmap.h
|
||||
glloadtests/shader-based/TextureCubemap.cpp
|
||||
glloadtests/shader-based/TextureCubemap.h
|
||||
glloadtests/shader-based/TexturedCube.cpp
|
||||
glloadtests/shader-based/TexturedCube.h
|
||||
glloadtests/utils/GLMeshLoader.hpp
|
||||
glloadtests/utils/GLTextureTranscoder.hpp
|
||||
)
|
||||
|
||||
|
||||
if(WIN32)
|
||||
if(NOT OPENGL_ES_EMULATOR)
|
||||
message("OPENGL_ES_EMULATOR not set. Will not build OpenGL ES load tests applications.")
|
||||
else()
|
||||
set(EMULATE_GLES ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(IOS OR EMULATE_GLES)
|
||||
# OpenGL ES 1.0
|
||||
create_gl_target( es1loadtests "ES1" "${ES1_SOURCES}" "${KTX_APP_ICON_PATH}" "${ES1_TEST_IMAGES}" SDL_GL_CONTEXT_PROFILE_ES 1 0 ON)
|
||||
endif()
|
||||
|
||||
if(IOS OR EMSCRIPTEN OR EMULATE_GLES)
|
||||
# OpenGL ES 3.0
|
||||
create_gl_target( es3loadtests "ES3" "${GL3_SOURCES}" "${LOAD_TEST_COMMON_RESOURCE_FILES}" "${GL3_TEST_IMAGES}" SDL_GL_CONTEXT_PROFILE_ES 3 0 ON YES)
|
||||
endif()
|
||||
|
||||
if( (APPLE AND NOT IOS) OR LINUX OR WIN32 )
|
||||
# OpenGL 3.3
|
||||
create_gl_target( gl3loadtests "GL3" "${GL3_SOURCES}" "${LOAD_TEST_COMMON_RESOURCE_FILES}" "${GL3_TEST_IMAGES}" SDL_GL_CONTEXT_PROFILE_CORE 3 3 OFF YES)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2015-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @file
|
||||
* @~English
|
||||
*
|
||||
* @brief Implementation of app for running a set of OpenGL load tests.
|
||||
*/
|
||||
|
||||
#include <exception>
|
||||
#include <sstream>
|
||||
#include <ktx.h>
|
||||
|
||||
#include "GLLoadTests.h"
|
||||
#include "ltexceptions.h"
|
||||
|
||||
GLLoadTests::GLLoadTests(const sampleInvocation samples[],
|
||||
const uint32_t numSamples,
|
||||
const char* const name,
|
||||
const SDL_GLProfile profile,
|
||||
const int majorVersion,
|
||||
const int minorVersion)
|
||||
: GLAppSDL(name, 640, 480, profile, majorVersion, minorVersion),
|
||||
siSamples(samples), sampleIndex(numSamples)
|
||||
{
|
||||
pCurSample = nullptr;
|
||||
}
|
||||
|
||||
GLLoadTests::~GLLoadTests()
|
||||
{
|
||||
delete pCurSample;
|
||||
}
|
||||
|
||||
bool
|
||||
GLLoadTests::initialize(Args& args)
|
||||
{
|
||||
bool explicitlyLoadOpenGL = false;
|
||||
for (uint32_t i = 1; i < args.size(); i++) {
|
||||
if (args[i].compare("--load-gl") == 0) {
|
||||
explicitlyLoadOpenGL = true;
|
||||
args.erase(args.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!GLAppSDL::initialize(args))
|
||||
return false;
|
||||
|
||||
// --load-gl allows testing of explicitly loading the OpenGL pointers.
|
||||
// Default is for ktxTexture_GLUpload to implicitly load them.
|
||||
if (explicitlyLoadOpenGL)
|
||||
ktxLoadOpenGL(reinterpret_cast<PFNGLGETPROCADDRESS>(SDL_GL_GetProcAddress));
|
||||
|
||||
for (auto it = args.begin() + 1; it != args.end(); it++) {
|
||||
infiles.push_back(*it);
|
||||
}
|
||||
if (infiles.size() > 0) {
|
||||
sampleIndex.setNumSamples((uint32_t)infiles.size());
|
||||
}
|
||||
// Launch the first sample.
|
||||
invokeSample(Direction::eForward);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
GLLoadTests::finalize()
|
||||
{
|
||||
delete pCurSample;
|
||||
GLAppSDL::finalize();
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
GLLoadTests::doEvent(SDL_Event* event)
|
||||
{
|
||||
bool result = false;
|
||||
switch (event->type) {
|
||||
case SDL_EVENT_KEY_UP:
|
||||
switch (event->key.key) {
|
||||
case 'q':
|
||||
quit = true;
|
||||
break;
|
||||
case 'n':
|
||||
++sampleIndex;
|
||||
invokeSample(Direction::eForward);
|
||||
break;
|
||||
case 'p':
|
||||
--sampleIndex;
|
||||
invokeSample(Direction::eBack);
|
||||
break;
|
||||
default:
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
// Forward to sample in case this is the start of motion.
|
||||
result = true;
|
||||
switch (event->button.button) {
|
||||
case SDL_BUTTON_LEFT:
|
||||
buttonDown.x = event->button.x;
|
||||
buttonDown.y = event->button.y;
|
||||
buttonDown.timestamp = event->button.timestamp;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
// Forward to sample so it doesn't get stuck in button down state.
|
||||
result = true;
|
||||
switch (event->button.button) {
|
||||
case SDL_BUTTON_LEFT:
|
||||
if (SDL_fabs(event->button.x - buttonDown.x) < 5
|
||||
&& SDL_fabs(event->button.y - buttonDown.y) < 5
|
||||
&& (event->button.timestamp - buttonDown.timestamp) < 100) {
|
||||
// Advance to the next sample.
|
||||
++sampleIndex;
|
||||
invokeSample(Direction::eForward);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
// On macOS drop events come also when Launch Pad sends a file open event.
|
||||
case SDL_EVENT_DROP_BEGIN:
|
||||
// Opens of multiple selected files from Finder/LaunchPad come as
|
||||
// a BEGIN, COMPLETE sequence per file. Only clear infiles after a
|
||||
// suitable pause between COMPLETE and BEGIN.
|
||||
if (event->drop.timestamp - dropCompleteTime > 500) {
|
||||
infiles.clear();
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_DROP_FILE:
|
||||
infiles.push_back(event->drop.data);
|
||||
break;
|
||||
case SDL_EVENT_DROP_COMPLETE:
|
||||
if (!infiles.empty()) {
|
||||
// Guard against the drop being text.
|
||||
dropCompleteTime = event->drop.timestamp;
|
||||
sampleIndex.setNumSamples((uint32_t)infiles.size());
|
||||
invokeSample(Direction::eForward);
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_USER:
|
||||
if (event->user.code == SwipeDetector::swipeGesture) {
|
||||
// This is horrible.
|
||||
uint64_t udirection = reinterpret_cast<uint64_t>(event->user.data1);
|
||||
SwipeDetector::Direction direction =
|
||||
static_cast<SwipeDetector::Direction>(udirection);
|
||||
switch (direction) {
|
||||
case SwipeDetector::Direction::left:
|
||||
++sampleIndex;
|
||||
invokeSample(Direction::eForward);
|
||||
break;
|
||||
case SwipeDetector::Direction::right:
|
||||
--sampleIndex;
|
||||
invokeSample(Direction::eBack);
|
||||
break;
|
||||
default:
|
||||
result = true;
|
||||
}
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result = swipeDetector.doEvent(event);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
// Further processing required.
|
||||
if (pCurSample != nullptr)
|
||||
result = pCurSample->doEvent(event); // Give sample a chance.
|
||||
if (result)
|
||||
return GLAppSDL::doEvent(event); // Pass to base class.
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
GLLoadTests::windowResized()
|
||||
{
|
||||
if (pCurSample != nullptr)
|
||||
pCurSample->resize(w_width, w_height);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
GLLoadTests::drawFrame(uint32_t msTicks)
|
||||
{
|
||||
if (pCurSample != nullptr)
|
||||
pCurSample->run(msTicks);
|
||||
|
||||
GLAppSDL::drawFrame(msTicks);
|
||||
}
|
||||
|
||||
#if 0
|
||||
void
|
||||
GLLoadTests::getOverlayText(GLTextOverlay * textOverlay)
|
||||
{
|
||||
if (enableTextOverlay && pCurSample != nullptr) {
|
||||
pCurSample->getOverlayText(textOverlay);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
GLLoadTests::invokeSample(Direction dir)
|
||||
{
|
||||
const sampleInvocation* sampleInv = &siSamples[sampleIndex];
|
||||
|
||||
delete pCurSample;
|
||||
// Certain events can be triggered during new sample initialization
|
||||
// while pCurSample is not valid, e.g. FOCUS_LOST. Protect against
|
||||
// problems from this by indicating there is no current sample.
|
||||
pCurSample = nullptr;
|
||||
|
||||
uint32_t unsupportedTypeExceptions = 0;
|
||||
std::string fileTitle;
|
||||
for (;;) {
|
||||
try {
|
||||
if (infiles.size() > 0) {
|
||||
fileTitle = "Viewing file ";
|
||||
fileTitle += infiles[sampleIndex];
|
||||
pCurSample = showFile(infiles[sampleIndex]);
|
||||
} else {
|
||||
sampleInv = &siSamples[sampleIndex];
|
||||
pCurSample = sampleInv->createSample(w_width, w_height,
|
||||
sampleInv->args,
|
||||
sBasePath);
|
||||
}
|
||||
break;
|
||||
} catch (unsupported_ctype& e) {
|
||||
(void)e; // To quiet unused variable warnings from some compilers.
|
||||
unsupportedTypeExceptions++;
|
||||
if (unsupportedTypeExceptions == sampleIndex.getNumSamples()) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
|
||||
infiles.size() > 0 ? fileTitle.c_str() : sampleInv->title,
|
||||
"None of the specified samples or files use texture types "
|
||||
"supported on this platform.",
|
||||
NULL);
|
||||
exit(0);
|
||||
} else {
|
||||
dir == Direction::eForward ? ++sampleIndex : --sampleIndex;
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
const SDL_MessageBoxButtonData buttons[] = {
|
||||
/* .flags, .buttonid, .text */
|
||||
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Continue" },
|
||||
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 1, "Abort" },
|
||||
};
|
||||
#if 0
|
||||
const SDL_MessageBoxColorScheme colorScheme = {
|
||||
{ /* .colors (.r, .g, .b) */
|
||||
/* [SDL_MESSAGEBOX_COLOR_BACKGROUND] */
|
||||
{ 255, 0, 0 },
|
||||
/* [SDL_MESSAGEBOX_COLOR_TEXT] */
|
||||
{ 0, 255, 0 },
|
||||
/* [SDL_MESSAGEBOX_COLOR_BUTTON_BORDER] */
|
||||
{ 255, 255, 0 },
|
||||
/* [SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND] */
|
||||
{ 0, 0, 255 },
|
||||
/* [SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED] */
|
||||
{ 255, 0, 255 }
|
||||
}
|
||||
};
|
||||
#endif
|
||||
const SDL_MessageBoxData messageboxdata = {
|
||||
SDL_MESSAGEBOX_ERROR, // .flags
|
||||
NULL, // .window
|
||||
infiles.size() > 0 ?
|
||||
fileTitle.c_str() : sampleInv->title, // .title
|
||||
e.what(), // .message
|
||||
SDL_arraysize(buttons), // .numbuttons
|
||||
buttons, // .buttons
|
||||
NULL //&colorScheme // .colorScheme
|
||||
};
|
||||
int buttonid;
|
||||
if (!SDL_ShowMessageBox(&messageboxdata, &buttonid)) {
|
||||
SDL_Log("error displaying error message box");
|
||||
exit(1);
|
||||
}
|
||||
if (buttonid == 0) {
|
||||
// Continue
|
||||
dir == Direction::eForward ? ++sampleIndex : --sampleIndex;
|
||||
} else {
|
||||
// We've been told to quit or no button was pressed.
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
setAppTitle(infiles.size() > 0 ? fileTitle.c_str() : sampleInv->title);
|
||||
pCurSample->resize(w_width, w_height);
|
||||
}
|
||||
|
||||
void
|
||||
GLLoadTests::onFPSUpdate()
|
||||
{
|
||||
// Using onFPSUpdate avoids rewriting the title every frame.
|
||||
//setWindowTitle(pCurSample->title);
|
||||
GLAppSDL::onFPSUpdate();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
#ifndef GL_LOAD_TESTS_H
|
||||
#define GL_LOAD_TESTS_H
|
||||
|
||||
/*
|
||||
* Copyright 2015-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @file
|
||||
* @~English
|
||||
*
|
||||
* @brief Definition of app for running a set of OpenGL load tests.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "GLAppSDL.h"
|
||||
#include "LoadTestSample.h"
|
||||
#include "SwipeDetector.h"
|
||||
|
||||
class GLLoadTests : public GLAppSDL {
|
||||
public:
|
||||
/** A table of samples and arguments */
|
||||
typedef struct sampleInvocation_ {
|
||||
const LoadTestSample::PFN_create createSample;
|
||||
const char* const args;
|
||||
const char* const title;
|
||||
} sampleInvocation;
|
||||
|
||||
GLLoadTests(const sampleInvocation samples[],
|
||||
const uint32_t numSamples,
|
||||
const char* const name,
|
||||
const SDL_GLProfile profile,
|
||||
const int majorVersion,
|
||||
const int minorVersion);
|
||||
virtual ~GLLoadTests();
|
||||
virtual bool doEvent(SDL_Event* event);
|
||||
virtual void drawFrame(uint32_t msTicks);
|
||||
virtual void finalize();
|
||||
//virtual void getOverlayText(TextOverlay* textOverlay, float yOffset);
|
||||
virtual bool initialize(Args& args);
|
||||
virtual void onFPSUpdate();
|
||||
virtual void windowResized();
|
||||
|
||||
protected:
|
||||
enum class Direction {
|
||||
eForward,
|
||||
eBack
|
||||
};
|
||||
void invokeSample(Direction dir);
|
||||
LoadTestSample* showFile(const std::string& filename);
|
||||
LoadTestSample* pCurSample;
|
||||
|
||||
bool quit = false;
|
||||
|
||||
const sampleInvocation* const siSamples;
|
||||
class sampleIndex {
|
||||
public:
|
||||
sampleIndex(const uint32_t numSamples) : numSamples(numSamples) {
|
||||
index = 0;
|
||||
}
|
||||
sampleIndex& operator++() {
|
||||
if (++index >= numSamples)
|
||||
index = 0;
|
||||
return *this;
|
||||
}
|
||||
sampleIndex& operator--() {
|
||||
if (--index > numSamples /* underflow */)
|
||||
index = numSamples-1;
|
||||
return *this;
|
||||
}
|
||||
operator int32_t() {
|
||||
return index;
|
||||
}
|
||||
uint32_t getNumSamples() { return numSamples; }
|
||||
void setNumSamples(uint32_t ns) { numSamples = ns; }
|
||||
protected:
|
||||
uint32_t numSamples;
|
||||
uint32_t index;
|
||||
} sampleIndex;
|
||||
|
||||
std::vector<std::string> infiles;
|
||||
|
||||
Sint64 dropCompleteTime = 0;
|
||||
struct {
|
||||
float x;
|
||||
float y;
|
||||
Sint64 timestamp;
|
||||
} buttonDown;
|
||||
|
||||
SwipeDetector swipeDetector;
|
||||
};
|
||||
|
||||
#endif /* GL_LOAD_TESTS_H */
|
||||
@@ -0,0 +1,250 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2018-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file DrawTexture.cpp
|
||||
* @brief Draw textures at actual size using the DrawTexture functions
|
||||
* from OES_draw_texture.
|
||||
*
|
||||
* @author Mark Callow
|
||||
*/
|
||||
|
||||
|
||||
#if defined(_WIN32)
|
||||
#if _MSC_VER < 1900
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <ktx.h>
|
||||
#include "disable_glm_warnings.h"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include "reenable_warnings.h"
|
||||
|
||||
#include "DrawTexture.h"
|
||||
#include "frame.h"
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
#if 0
|
||||
static int isPowerOfTwo (int x)
|
||||
{
|
||||
if (x < 0) x = -1;
|
||||
return ((x != 0) && !(x & (x - 1)));
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
LoadTestSample*
|
||||
DrawTexture::create(uint32_t width, uint32_t height,
|
||||
const char* const szArgs,
|
||||
const std::string sBasePath)
|
||||
{
|
||||
return new DrawTexture(width, height, szArgs, sBasePath);
|
||||
}
|
||||
|
||||
DrawTexture::DrawTexture(uint32_t width, uint32_t height,
|
||||
const char* const szArgs,
|
||||
const std::string sBasePath)
|
||||
: LoadTestSample(width, height, sBasePath)
|
||||
{
|
||||
GLint iCropRect[4] = {0, 0, 0, 0};
|
||||
const GLchar* szExtensions = (const GLchar*)glGetString(GL_EXTENSIONS);
|
||||
const char* filename;
|
||||
std::string pathname;
|
||||
GLenum target;
|
||||
GLboolean npotTexture;
|
||||
GLenum glerror;
|
||||
ktxTexture* kTexture;
|
||||
KTX_error_code ktxresult;
|
||||
GLint sign_s = 1, sign_t = 1;
|
||||
|
||||
bInitialized = false;
|
||||
gnTexture = 0;
|
||||
|
||||
|
||||
if (strstr(szExtensions, "OES_draw_texture") != NULL) {
|
||||
glDrawTexsOES = (PFNGLDRAWTEXSOESPROC)SDL_GL_GetProcAddress("glDrawTexsOES");
|
||||
glDrawTexiOES = (PFNGLDRAWTEXIOESPROC)SDL_GL_GetProcAddress("glDrawTexiOES");
|
||||
glDrawTexxOES = (PFNGLDRAWTEXXOESPROC)SDL_GL_GetProcAddress("glDrawTexxOES");
|
||||
glDrawTexfOES = (PFNGLDRAWTEXFOESPROC)SDL_GL_GetProcAddress("glDrawTexfOES");
|
||||
glDrawTexsvOES = (PFNGLDRAWTEXSVOESPROC)SDL_GL_GetProcAddress("glDrawTexsvOES");
|
||||
glDrawTexivOES = (PFNGLDRAWTEXIVOESPROC)SDL_GL_GetProcAddress("glDrawTexivOES");
|
||||
glDrawTexxvOES = (PFNGLDRAWTEXXVOESPROC)SDL_GL_GetProcAddress("glDrawTexxvOES");
|
||||
glDrawTexfvOES = (PFNGLDRAWTEXFVOESPROC)SDL_GL_GetProcAddress("glDrawTexfvOES");
|
||||
} else {
|
||||
/* Can't do anything */
|
||||
std::stringstream message;
|
||||
|
||||
message << "DrawTexture: this OpenGL ES implementation does not support"
|
||||
<< " OES_draw_texture. Can't Run Test";
|
||||
throw std::runtime_error(message.str());
|
||||
}
|
||||
|
||||
if (strstr(szExtensions, "OES_texture_npot") != NULL)
|
||||
bNpotSupported = GL_TRUE;
|
||||
else
|
||||
bNpotSupported = GL_FALSE;
|
||||
|
||||
|
||||
if ((filename = strchr(szArgs, ' ')) != NULL) {
|
||||
npotTexture = GL_FALSE;
|
||||
if (!strncmp(szArgs, "--npot ", 7)) {
|
||||
npotTexture = GL_TRUE;
|
||||
#if defined(DEBUG)
|
||||
} else {
|
||||
assert(0); /* Unknown argument in sampleInvocations */
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
filename = szArgs;
|
||||
npotTexture = GL_FALSE;
|
||||
}
|
||||
|
||||
if (npotTexture && !bNpotSupported) {
|
||||
/* Load error texture. */
|
||||
filename = "no-npot.ktx";
|
||||
}
|
||||
|
||||
pathname = getAssetPath() + filename;
|
||||
|
||||
ktxresult = ktxTexture_CreateFromNamedFile(pathname.c_str(),
|
||||
KTX_TEXTURE_CREATE_NO_FLAGS,
|
||||
&kTexture);
|
||||
if (KTX_SUCCESS != ktxresult) {
|
||||
std::stringstream message;
|
||||
|
||||
message << "Creation of ktxTexture from \"" << pathname
|
||||
<< "\" failed: " << ktxErrorString(ktxresult);
|
||||
throw std::runtime_error(message.str());
|
||||
}
|
||||
ktxresult = ktxTexture_GLUpload(kTexture, &gnTexture, &target, &glerror);
|
||||
|
||||
if (KTX_SUCCESS == ktxresult) {
|
||||
if (target != GL_TEXTURE_2D) {
|
||||
/* Can only draw 2D textures */
|
||||
glDeleteTextures(1, &gnTexture);
|
||||
return;
|
||||
}
|
||||
|
||||
if (kTexture->orientation.x == KTX_ORIENT_X_LEFT)
|
||||
sign_s = -1;
|
||||
if (kTexture->orientation.y == KTX_ORIENT_Y_DOWN)
|
||||
sign_t = -1;
|
||||
|
||||
iCropRect[2] = uTexWidth = kTexture->baseWidth;
|
||||
iCropRect[3] = uTexHeight = kTexture->baseHeight;
|
||||
iCropRect[2] *= sign_s;
|
||||
iCropRect[3] *= sign_t;
|
||||
|
||||
glEnable(target);
|
||||
|
||||
if (kTexture->numLevels > 1)
|
||||
// Enable bilinear mipmapping.
|
||||
// TO DO: application can consider inserting a key,value pair in
|
||||
// the KTX file that indicates what type of filtering to use.
|
||||
glTexParameteri(target,
|
||||
GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
|
||||
else
|
||||
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
|
||||
glTexParameteriv(target, GL_TEXTURE_CROP_RECT_OES, iCropRect);
|
||||
|
||||
/* Check for any errors */
|
||||
assert(GL_NO_ERROR == glGetError());
|
||||
|
||||
ktxTexture_Destroy(kTexture);
|
||||
} else {
|
||||
std::stringstream message;
|
||||
|
||||
message << "Load of texture from \"" << pathname << "\" failed: ";
|
||||
if (ktxresult == KTX_GL_ERROR) {
|
||||
message << std::showbase << "GL error " << std::hex << glerror
|
||||
<< "occurred.";
|
||||
} else {
|
||||
message << ktxErrorString(ktxresult);
|
||||
}
|
||||
throw std::runtime_error(message.str());
|
||||
}
|
||||
|
||||
glClearColor(0.4f, 0.4f, 0.5f, 1.0f);
|
||||
glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glVertexPointer(3, GL_BYTE, 0, (GLvoid *)frame_position);
|
||||
|
||||
bInitialized = GL_TRUE;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
DrawTexture::~DrawTexture()
|
||||
{
|
||||
if (bInitialized) {
|
||||
glDeleteTextures(1, &gnTexture);
|
||||
}
|
||||
assert(GL_NO_ERROR == glGetError());
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
void
|
||||
DrawTexture::resize(uint32_t uNewWidth, uint32_t uNewHeight)
|
||||
{
|
||||
glViewport(0, 0, uNewWidth, uNewHeight);
|
||||
this->uWidth = uNewWidth;
|
||||
this->uHeight = uNewHeight;
|
||||
|
||||
// Set up an orthographic projection where 1 = 1 pixel
|
||||
framePMatrix = glm::ortho(0.f, (float)uWidth, 0.f, (float)uHeight);
|
||||
// Move (0,0,0) to the center of the window.
|
||||
framePMatrix *= glm::translate(glm::mat4(),
|
||||
glm::vec3((float)uWidth/2, (float)uHeight/2, 0));
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadMatrixf(glm::value_ptr(framePMatrix));
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
// Scale the frame to fill the viewport. To guarantee its lines
|
||||
// appear we need to inset them by half-a-pixel hence the -1.
|
||||
// [Lines at the edges of the clip volume may or may not appear
|
||||
// depending on the OpenGL ES implementation. This is because
|
||||
// (a) the edges are on the points of the diamonds of the diamond
|
||||
// exit rule and slight precision errors can easily push the
|
||||
// lines outside the diamonds.
|
||||
// (b) the specification allows lines to be up to 1 pixel either
|
||||
// side of the exact position.]
|
||||
glLoadIdentity();
|
||||
glScalef((float)(uWidth - 1) / 2, (float)(uHeight - 1) / 2, 1);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
void
|
||||
DrawTexture::run(uint32_t /*msTicks*/)
|
||||
{
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glDrawArrays(GL_LINE_LOOP, 0, 4);
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glDrawTexiOES(uWidth/2 - uTexWidth/2,
|
||||
(uHeight)/2 - uTexHeight/2,
|
||||
0,
|
||||
uTexWidth, uTexHeight);
|
||||
|
||||
assert(GL_NO_ERROR == glGetError());
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
@@ -0,0 +1,66 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2018-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file DrawTexture.h
|
||||
* @brief Draw textures at actual size using the DrawTexture functions
|
||||
* from OES_draw_texture.
|
||||
*
|
||||
* @author Mark Callow
|
||||
*/
|
||||
|
||||
#ifndef DRAW_TEXTURE_H
|
||||
#define DRAW_TEXTURE_H
|
||||
|
||||
#include <GLES/gl.h>
|
||||
#include <GLES/glext.h>
|
||||
|
||||
#include "LoadTestSample.h"
|
||||
|
||||
class DrawTexture : public LoadTestSample {
|
||||
public:
|
||||
DrawTexture(uint32_t width, uint32_t height,
|
||||
const char* const szArgs,
|
||||
const std::string sBasePath);
|
||||
~DrawTexture();
|
||||
|
||||
virtual void resize(uint32_t width, uint32_t height);
|
||||
virtual void run(uint32_t msTicks);
|
||||
|
||||
//virtual void getOverlayText(GLTextOverlay *textOverlay);
|
||||
|
||||
static LoadTestSample*
|
||||
create(uint32_t width, uint32_t height,
|
||||
const char* const szArgs, const std::string sBasePath);
|
||||
|
||||
protected:
|
||||
PFNGLDRAWTEXSOESPROC glDrawTexsOES;
|
||||
PFNGLDRAWTEXIOESPROC glDrawTexiOES;
|
||||
PFNGLDRAWTEXXOESPROC glDrawTexxOES;
|
||||
PFNGLDRAWTEXFOESPROC glDrawTexfOES;
|
||||
PFNGLDRAWTEXSVOESPROC glDrawTexsvOES;
|
||||
PFNGLDRAWTEXIVOESPROC glDrawTexivOES;
|
||||
PFNGLDRAWTEXXVOESPROC glDrawTexxvOES;
|
||||
PFNGLDRAWTEXFVOESPROC glDrawTexfvOES;
|
||||
|
||||
uint32_t uWidth;
|
||||
uint32_t uHeight;
|
||||
|
||||
uint32_t uTexWidth;
|
||||
uint32_t uTexHeight;
|
||||
|
||||
glm::mat4 framePMatrix;
|
||||
|
||||
GLuint gnTexture;
|
||||
|
||||
bool bNpotSupported;
|
||||
|
||||
bool bInitialized;
|
||||
};
|
||||
|
||||
#endif /* DRAW_TEXTURE_H */
|
||||
@@ -0,0 +1,109 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2018-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file ES1LoadTests.cpp
|
||||
* @brief List of tests of the KTX loader for OpenGL ES 1.1.
|
||||
*
|
||||
* The loader is tested by loading and drawing KTX textures in various formats
|
||||
* using the DrawTexture and TexturedCube samples.
|
||||
*/
|
||||
|
||||
#include <sstream>
|
||||
#include <ktx.h>
|
||||
|
||||
#include "GLLoadTests.h"
|
||||
#include "DrawTexture.h"
|
||||
#include "TexturedCube.h"
|
||||
|
||||
LoadTestSample*
|
||||
GLLoadTests::showFile(const std::string& filename)
|
||||
{
|
||||
KTX_error_code ktxresult;
|
||||
ktxTexture* kTexture;
|
||||
ktxresult = ktxTexture_CreateFromNamedFile(filename.c_str(),
|
||||
KTX_TEXTURE_CREATE_NO_FLAGS,
|
||||
&kTexture);
|
||||
if (KTX_SUCCESS != ktxresult) {
|
||||
std::stringstream message;
|
||||
|
||||
message << "Creation of ktxTexture from \"" << getAssetPath()
|
||||
<< filename << "\" failed: " << ktxErrorString(ktxresult);
|
||||
throw std::runtime_error(message.str());
|
||||
}
|
||||
|
||||
LoadTestSample::PFN_create createViewer;
|
||||
LoadTestSample* pViewer;
|
||||
createViewer = DrawTexture::create;
|
||||
ktxTexture_Destroy(kTexture);
|
||||
pViewer = createViewer(w_width, w_height, filename.c_str(), "");
|
||||
return pViewer;
|
||||
}
|
||||
|
||||
const GLLoadTests::sampleInvocation siSamples[] = {
|
||||
{ DrawTexture::create,
|
||||
"--npot hi_mark.ktx",
|
||||
"RGB8 NPOT HI Logo"
|
||||
},
|
||||
{ DrawTexture::create,
|
||||
"--npot luminance-reference-metadata.ktx",
|
||||
"LUMINANCE8 NPOT"
|
||||
},
|
||||
{ DrawTexture::create,
|
||||
"orient-up-metadata.ktx",
|
||||
"RGB8 + KTXOrientation up"
|
||||
},
|
||||
{ DrawTexture::create,
|
||||
"orient-down-metadata.ktx",
|
||||
"RGB8 + KTXOrientation down"
|
||||
},
|
||||
{ DrawTexture::create,
|
||||
"etc1.ktx",
|
||||
"ETC1 RGB8"
|
||||
},
|
||||
{ DrawTexture::create,
|
||||
"etc2-rgb.ktx",
|
||||
"ETC2 RGB8"
|
||||
},
|
||||
{ DrawTexture::create,
|
||||
"etc2-rgba1.ktx",
|
||||
"ETC2 RGB8A1"
|
||||
},
|
||||
{ DrawTexture::create,
|
||||
"etc2-rgba8.ktx",
|
||||
"ETC2 RGB8A8"
|
||||
},
|
||||
{ DrawTexture::create,
|
||||
"rgba-reference.ktx",
|
||||
"RGBA8"
|
||||
},
|
||||
{ TexturedCube::create,
|
||||
"rgb-reference.ktx",
|
||||
"RGB8"
|
||||
},
|
||||
{ TexturedCube::create,
|
||||
"rgb-amg-reference.ktx",
|
||||
"RGB8 + Auto Mipmap"
|
||||
},
|
||||
{ TexturedCube::create,
|
||||
"rgb-mipmap-reference.ktx",
|
||||
"RGB8 Color/level mipmap"
|
||||
},
|
||||
{ TexturedCube::create,
|
||||
"--npot hi_mark_sq.ktx",
|
||||
"RGB8 NPOT HI Logo"
|
||||
},
|
||||
};
|
||||
|
||||
const int iNumSamples = sizeof(siSamples) / sizeof(GLLoadTests::sampleInvocation);
|
||||
|
||||
AppBaseSDL* theApp = new GLLoadTests(siSamples, iNumSamples,
|
||||
"KTX Loader Tests for OpenGL ES 1",
|
||||
SDL_GL_CONTEXT_PROFILE_ES, 1, 1);
|
||||
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2018-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file TexturedCube.cpp
|
||||
* @brief Draw a textured cube.
|
||||
*/
|
||||
|
||||
#if defined(_WIN32)
|
||||
#if _MSC_VER < 1900
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <ktx.h>
|
||||
#include "disable_glm_warnings.h"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include "reenable_warnings.h"
|
||||
|
||||
#include "TexturedCube.h"
|
||||
#include "cube.h"
|
||||
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
LoadTestSample*
|
||||
TexturedCube::create(uint32_t width, uint32_t height,
|
||||
const char* const szArgs,
|
||||
const std::string sBasePath)
|
||||
{
|
||||
return new TexturedCube(width, height, szArgs, sBasePath);
|
||||
}
|
||||
|
||||
TexturedCube::TexturedCube(uint32_t width, uint32_t height,
|
||||
const char* const szArgs,
|
||||
const std::string sBasePath)
|
||||
: LoadTestSample(width, height, sBasePath)
|
||||
{
|
||||
const GLchar* szExtensions = (const GLchar*)glGetString(GL_EXTENSIONS);
|
||||
const char* filename;
|
||||
std::string pathname;
|
||||
GLuint gnTexture = 0;
|
||||
GLenum target;
|
||||
GLenum glerror;
|
||||
GLboolean npotSupported, npotTexture;
|
||||
ktxTexture* kTexture;
|
||||
KTX_error_code ktxresult;
|
||||
|
||||
if (strstr(szExtensions, "OES_texture_npot") != NULL)
|
||||
npotSupported = GL_TRUE;
|
||||
else
|
||||
npotSupported = GL_FALSE;
|
||||
|
||||
if ((filename = strchr(szArgs, ' ')) != NULL) {
|
||||
npotTexture = GL_FALSE;
|
||||
if (!strncmp(szArgs, "--npot ", 7)) {
|
||||
npotTexture = GL_TRUE;
|
||||
#if defined(DEBUG)
|
||||
} else {
|
||||
assert(0); /* Unknown argument in sampleInvocations */
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
filename = szArgs;
|
||||
npotTexture = GL_FALSE;
|
||||
}
|
||||
|
||||
if (npotTexture && !npotSupported) {
|
||||
/* Load error texture. */
|
||||
filename = "no-npot.ktx";
|
||||
}
|
||||
pathname = getAssetPath() + filename;
|
||||
|
||||
ktxresult = ktxTexture_CreateFromNamedFile(pathname.c_str(),
|
||||
KTX_TEXTURE_CREATE_NO_FLAGS,
|
||||
&kTexture);
|
||||
if (KTX_SUCCESS != ktxresult) {
|
||||
std::stringstream message;
|
||||
|
||||
message << "Creation of ktxTexture from \"" << pathname
|
||||
<< "\" failed: " << ktxErrorString(ktxresult);
|
||||
throw std::runtime_error(message.str());
|
||||
}
|
||||
ktxresult = ktxTexture_GLUpload(kTexture, &gnTexture, &target, &glerror);
|
||||
|
||||
if (KTX_SUCCESS == ktxresult) {
|
||||
if (target != GL_TEXTURE_2D) {
|
||||
/* Can only draw 2D textures */
|
||||
glDeleteTextures(1, &gnTexture);
|
||||
return;
|
||||
}
|
||||
glEnable(target);
|
||||
|
||||
if (kTexture->numLevels > 1)
|
||||
// Enable bilinear mipmapping.
|
||||
// TO DO: application can consider inserting a key,value pair in
|
||||
// the KTX file that indicates what type of filtering to use.
|
||||
glTexParameteri(target,
|
||||
GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
|
||||
else
|
||||
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
|
||||
|
||||
ktxTexture_Destroy(kTexture);
|
||||
} else {
|
||||
std::stringstream message;
|
||||
|
||||
message << "Load of texture from \"" << pathname << "\" failed: ";
|
||||
if (ktxresult == KTX_GL_ERROR) {
|
||||
message << std::showbase << "GL error " << std::hex << glerror
|
||||
<< "occurred.";
|
||||
} else {
|
||||
message << ktxErrorString(ktxresult);
|
||||
}
|
||||
throw std::runtime_error(message.str());
|
||||
}
|
||||
|
||||
/* By default dithering is enabled. Dithering does not provide visual
|
||||
* improvement in this sample so disable it to improve performance.
|
||||
*/
|
||||
glDisable(GL_DITHER);
|
||||
|
||||
glEnable(GL_CULL_FACE);
|
||||
glClearColor(0.2f,0.3f,0.4f,1.0f);
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
|
||||
glVertexPointer(3, GL_FLOAT, 0, cube_face);
|
||||
glColorPointer(4, GL_FLOAT, 0, cube_color);
|
||||
glTexCoordPointer(2, GL_FLOAT, 0, cube_texture);
|
||||
}
|
||||
|
||||
TexturedCube::~TexturedCube()
|
||||
{
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glEnable(GL_DITHER);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
assert(GL_NO_ERROR == glGetError());
|
||||
}
|
||||
|
||||
void
|
||||
TexturedCube::resize(uint32_t uWidth, uint32_t uHeight)
|
||||
{
|
||||
glm::mat4 matProj;
|
||||
glViewport(0, 0, uWidth, uHeight);
|
||||
|
||||
glMatrixMode( GL_PROJECTION );
|
||||
matProj = glm::perspective(glm::radians(45.f),
|
||||
uWidth / (float)uHeight,
|
||||
1.f, 100.f);
|
||||
glLoadIdentity();
|
||||
glLoadMatrixf(glm::value_ptr(matProj));
|
||||
|
||||
glMatrixMode( GL_MODELVIEW );
|
||||
assert(GL_NO_ERROR == glGetError());
|
||||
}
|
||||
|
||||
void
|
||||
TexturedCube::run(uint32_t msTicks)
|
||||
{
|
||||
/* Setup the view matrix : just turn around the cube. */
|
||||
const float fDistance = 5.0f;
|
||||
glm::vec3 eye((float)cos( msTicks*0.001f ) * fDistance,
|
||||
(float)sin( msTicks*0.0007f ) * fDistance,
|
||||
(float)sin( msTicks*0.001f ) * fDistance);
|
||||
glm::vec3 look(0.,0.,0.);
|
||||
glm::vec3 up(0.,1.,0.);
|
||||
glm::mat4 matView = glm::lookAt( eye, look, up );
|
||||
|
||||
glLoadIdentity();
|
||||
glLoadMatrixf(glm::value_ptr(matView));
|
||||
|
||||
/* Draw */
|
||||
glClear( GL_COLOR_BUFFER_BIT );
|
||||
|
||||
glDrawElements(GL_TRIANGLES, CUBE_NUM_INDICES,
|
||||
GL_UNSIGNED_SHORT, cube_index_buffer);
|
||||
|
||||
assert(GL_NO_ERROR == glGetError());
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2018-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file TexturedCube.cpp
|
||||
* @brief Draw a textured cube.
|
||||
*/
|
||||
|
||||
#ifndef TEXTURED_CUBE_H
|
||||
#define TEXTURED_CUBE_H
|
||||
|
||||
#include <GLES/gl.h>
|
||||
#include <GLES/glext.h>
|
||||
|
||||
#include "LoadTestSample.h"
|
||||
class TexturedCube : public LoadTestSample {
|
||||
public:
|
||||
TexturedCube(uint32_t width, uint32_t height,
|
||||
const char* const szArgs,
|
||||
const std::string sBasePath);
|
||||
~TexturedCube();
|
||||
|
||||
virtual void resize(uint32_t width, uint32_t height);
|
||||
virtual void run(uint32_t msTicks);
|
||||
|
||||
//virtual void getOverlayText(GLTextOverlay *textOverlay);
|
||||
|
||||
static LoadTestSample*
|
||||
create(uint32_t width, uint32_t height,
|
||||
const char* const szArgs, const std::string sBasePath);
|
||||
};
|
||||
|
||||
#endif /* TEXTURED_CUBE_H */
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright 2015-2024 Mark Callow
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
WARNING: Do not attempt to edit the Info.plist you see in the Xcode
|
||||
project. It is unavoidably configured from this by CMake. Edit here.
|
||||
-->
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string></string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<string>YES</string>
|
||||
</dict>
|
||||
</plist>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user