Skip to content

CLI Samples launcher + Vulkan Hello Triangle #493

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

TomAtkinsonArm
Copy link
Contributor

@TomAtkinsonArm TomAtkinsonArm commented Jul 9, 2022

Description

Blocked by #485 #487 #488 #490

Large diff as this contains commits from several branches

Currently only works on Ubuntu with XLIB enabled as I have not added every window and surface type yet

loading sample configs
candidate: /build/linux/cmd/samples_launcher/samples.json
selected: /build/linux/cmd/samples_launcher/samples.json
looking for: libsample__hello_sample.so - not found
looking for: libsample__hello_triangle.so
candidate: ./build/linux/samples/hello_triangle/lib/Debug/x86_64/libsample__hello_triangle.so
selecting: ./build/linux/samples/hello_triangle/lib/Debug/x86_64/libsample__hello_triangle.so


Available Samples: (1/2)

id:             hello_triangle
name:           Hello Triangle
description:    Vulkan Hello Triangle
compile target: sample__hello_triangle

Hello Triangle!

samples.json is maintained by CMake automatically and contains metadata for all registered samples. Samples may be available or not, we cant easily track which have been built previously and which haven't. The Sample Launcher deduces available samples by searching the current working directory for platform specific runtime libraries for a given sample. Available samples are then displayed in a list.

The code sample shows the launcher being run with hello_triangle. The runtime library is found as stated in the lookup log. The launcher then loads this sample and runs its sample_main.

Screenshot from 2022-07-09 18-44-13

Screenshot from 2022-07-09 18-41-06

After a little bit more work I ported Hello Triangle

Screenshot from 2022-07-09 22-51-06

Solved Problems

Compilation

Single samples can be compiled by using the sample__<sample_name> target. All samples can be compiled with vkb__samples. samples_launcher is only compiled when one or more samples are selected for compilation. A user should never need to manually compile samples_launcher.

As the framework is now split into components a sample and the launcher will only compile the targets needed. For instance, compiling the current samples yields sub second compile times. There is also no link step so serialized link times in our current builds will not occur in this version of the framework.

Samples

Samples no longer need to follow strict patterns for creation. Requirements for a sample are:

  • A file defining sample_main
  • vkb__register_sample - sample build target
  • vkb__register_sample_descriptor - sample metadata

From here samples can come in any shape or size. The most appropriate execution flow can be used for each sample. We may want to define a component which acts as a base sample to reduce boilerplate, but these building blocks do not need to be the same for all samples.

TODO

  • - condense launcher code
  • - add a default samples build test which makes sure the launcher can find all samples when building with vkb__samples

@TomAtkinsonArm TomAtkinsonArm self-assigned this Jul 9, 2022
@TomAtkinsonArm TomAtkinsonArm added the framework This is relevant to the framework label Jul 9, 2022
@TomAtkinsonArm TomAtkinsonArm changed the title WIP: CLI Samples launcher CLI Samples launcher Jul 9, 2022
@TomAtkinsonArm TomAtkinsonArm changed the title CLI Samples launcher CLI Samples launcher + Vulkan Hello Triangle Jul 9, 2022
@TomAtkinsonArm TomAtkinsonArm force-pushed the samples_launcher branch 2 times, most recently from 1a3f091 to 0311ace Compare July 10, 2022 23:15
@TomAtkinsonArm TomAtkinsonArm mentioned this pull request Jul 18, 2022
@@ -32,6 +32,8 @@ function(vkb__register_component)

add_library("vkb__${TARGET_NAME}" STATIC ${TARGET_SRC})

set_property(TARGET "vkb__${TARGET_NAME}" PROPERTY POSITION_INDEPENDENT_CODE ON) # PIC required for shared lib linking
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is probably better to use set_compile_options("vkb_${TARGET_NAME}" PRIVATE POSITION_INDEPENDENT_CODE ON)
NB: prior to CMake 3.19, -fPIC didn't mean that pie is also by default mandated in cmake other than in Android builds. I think we're mandating a recent enough version for this to not be an issue. Just listing here as a just incase.

endif()

if(MSVC)
target_compile_options("sample__${TARGET_NAME}" PRIVATE /W4 /WX)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dunno if you get any warnings in MSVC when compiling with this due to /W3 and /Md being set automatically D9025 might crop up. However, just in case it happens:
https://cmake.org/cmake/help/v3.15/policy/CMP0092.html
https://cmake.org/cmake/help/v3.15/policy/CMP0091.html
add:
if (POLICY CMP0091)
cmake_policy(SET CMP0091 NEW)
endif ()
if (POLICY CMP0092)
cmake_policy(SET CMP0092 NEW)
endif ()

TomAtkinsonArm and others added 5 commits July 29, 2022 22:05
* event bus

* add AbstractChannel comment

* response to feedback

* add assert

* assert

* added observer expiration test
* fix folder structure in Visual Studio

* add interface library support

* review changes
* std filesystem

more tests and fixes

recursive tests

more tests

remove order specific checks from tests

review comments

* fix duplicated check

* review changes
* event bus

* response to feedback

* add assert

* assert

* added observer expiration test

* add pipelines

* stages with custom fields

* Pipeline -> EventPipeline

* add comments
message("ADDING STATIC: vkb__${TARGET_NAME}")

add_library("vkb__${TARGET_NAME}" STATIC ${TARGET_SRC})
add_library("vkb__${TARGET_NAME}" STATIC ${TARGET_SRC} ${TARGET_HEADERS})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think this is the same as in 490? Not sure of the advantage of adding headers explicitly to the cmake target.

SHADER_FILES_GLSL
${TARGET_SHADER_FILES_GLSL})
get_property(DESCRIPTORS GLOBAL PROPERTY SAMPLE_DESCRIPTORS)
list(APPEND DESCRIPTORS "{\"id\":\"${TARGET_ID}\",\"name\":\"${TARGET_NAME}\", \"description\": \"${TARGET_DESCRIPTION}\", \"library_name\": \"${TARGET_LIB}\"}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could potentially use jq or https://github.com/sbellus/json-cmake or some such to make this a usable thing at the cmake level. however, this works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this allow for creating json structs in cmake? Or just parsing?

@TomAtkinsonArm
Copy link
Contributor Author

@SaschaWillems @gary-sweet @asuessenbach can you please take a look at this PR when you get a chance? Looking for general feedback on how these components fit together and how samples can be written. The idea is to remove all the constraints on samples so that they can be more flexible in the future.

An example of this is sample_main which is all that is required to create a sample. Or EventPipeline which allows a sample to define its own lifecycle. "Frameworks" can be extracted into a series of default configurations for these components as most components are configurable or extendable

@asuessenbach
Copy link
Contributor

First of all: the framework compiles without any issues with VS2022.
But when I start the samples_launcher, I get the following message:

loading sample configs
[vfs/std_filesystem.cpp:208] path does not exist: C:\git\Vulkan-Samples\build\VS2022_64\cmd\samples_launcher\build

And, indeed, that path does not exist. I found the file samples.json directly in the samples_launcher directory; there's no build directory there.
For a thorough review, I might need some more time. After all, this is pretty big.

};

// Load a config from a binary buffer
components::StackErrorPtr load_config_from_json(const std::vector<uint8_t> &data, Config *config);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, we agreed to switch to std::exceptions here, instead of components::StackErrorPtr?
(Funnily, components::StackErrorPtr derives from std::exception, but is not used by the throw/catch mechanism of exceptions!)


std::cout << "loading sample configs\n";

std::vector<std::string> files;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename files to config_files, or so?


std::cout << "looking for: " << library_name;

std::vector<std::string> candidates;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you expect more than one candidate per sample? But you just use the first one.
Maybe the search in the sample_files could then be done by std::find_if:

		auto sample_it = std::find_if(sample_files.begin(),
		                              sample_files.end(),
		                              [&library_name](auto const &sample_file) { return vfs::helpers::get_file_name(sample_file) == library_name; });
		if (sample_it == sample_files.end())
		{
			std::cout << " - not found\n";
			continue;
		}

return EXIT_SUCCESS;
}

for (auto &info : available_samples)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use std::find_if, again?

  auto sample_it =
	    std::find_if(available_samples.begin(), available_samples.end(), [&args](auto &sample_info) { return sample_info.sample.id == args[0]; });
	if (sample_it == available_samples.end())
	{
		std::cout << "Missing sample " << args[0] << "\n";
	}


#define HANDLE_JSON_ERROR_START() \
try \
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not hide scope opening in a macro. Functions like marshall_json below look somewhat awkward then.
Maybe just some HANDLE_JSON_ERROR representing the catch block would be enough?

}

template <>
class JsonMarshaler<config::Sample>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The termini Marshaler and UnMarshaler are a bit alien to me.
Maybe something like Serializer/Deserializer or Encoder/Decoder would be a bit more descriptive?

{
touch.position.x = event.pos_x - touch.position.x;
touch.position.y = event.pos_y - touch.position.y;
touch.position.x = event.x - touch.position.x;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this supposed to do?
It's unconditionally overwritten next.


inline size_t unobserved_each_event_count() const
{
size_t unprocessed_events{0};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe

  return std::accumulate(
    m_each_callbacks.begin(), m_each_callbacks.end(), 0, []( size_t acc, auto const & callback ) { return acc + callback.second->queue_size(); } );

And similar for the other count functions.

// on a second pass we expect only the "then" stages to be processed
execution_index = 2;

pipeline.process();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how the correct order is verified. Could you please elaborate a bit?


REQUIRE(one_execution_count == 1);
REQUIRE(two_execution_count == 4);
REQUIRE(three_execution_count == 4);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, the number of calls of each event is checked, but how is the correct order verified?

Copy link
Contributor

@asuessenbach asuessenbach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Windows using VS, I had to set the Working Directory to the root of the repository in order to find everything. I don't see that mentioned anywhere.

# undef IMPORT
# define EXPORT __declspec(dllexport)
# define IMPORT __declspec(dllimport)
#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe better to read:

#if defined(_WIN32)
#  define EXPORT __declspec(dllexport)
#  define IMPORT __declspec(dllimport)
#else
#  define EXPORT
#  define IMPORT
#endif

* @param name library name
* @return std::string OS library name
*/
std::string os_library_name(const std::string &name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixing functions taking a const std::string & with functions taking a const char * in the same header seems counter-intuitive. I would stay with one of them, keeping the same level of abstraction.

*/
struct PlatformContext
{
virtual std::vector<std::string> arguments() const = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you make this function pure virtual?
Probably all Context types just hold the arguments in a vector, that is it could be as well be moved into PlatformContext. Then, you don't even need that accessor function.

*
* @return int status code
*/
int platform_main(components::PlatformContext *);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't platform_main take a const &, or maybe a const *?

std::stringstream lib_name;
lib_name << os_library_prefix() << name << os_library_postfix();
return lib_name.str();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to use a std::stringstream here. Simply
return os_library_prefix() + name + os_library_postfix();


if (!validate_extensions(required_device_extensions, device_extensions))
{
throw std::runtime_error("Required device extensions are missing, will try without.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean with "will try without" ?


bool validate_extensions(const std::vector<const char *> & required,
const std::vector<VkExtensionProperties> &available)
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe:

	// inner find_if returns true if the extension was not found
	// outer find_if returns true if none of the extensions were not found, that is if all extensions were found
	return std::find_if(required.begin(),
	                    required.end(),
	                    [&available](auto required_extension) {
		                    return std::find_if(available.begin(),
		                                        available.end(),
		                                        [&required_extension](auto const &available_extension) {
			                                        return strcmp(available_extension.extensionName, required_extension) == 0;
		                                        }) == available.end();
	                    }) == required.end();

bool validate_layers(const std::vector<const char *> & required,
const std::vector<VkLayerProperties> &available)
{
for (auto extension : required)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe:

	// inner find_if returns true if the layer was not found
	// outer find_if returns true if none of the layers was not found, that is if all layers were found
	return std::find_if(required.begin(),
	                    required.end(),
	                    [&available](auto required_layer) {
		                    return std::find_if(available.begin(),
		                                        available.end(),
		                                        [&required_layer](auto const &available_layer) {
			                                        return strcmp(available_layer.layerName, required_layer) == 0;
		                                        }) == available.end();
	                    }) == required.end();

{
LOGI("Initializing vulkan instance.");

if (volkInitialize())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe:
if (volkInitialize() != VK_SUCCESS)

}

format.format = VK_FORMAT_UNDEFINED;
for (auto &candidate : formats)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some std::find_if here?

@tomadamatkinson tomadamatkinson marked this pull request as draft May 22, 2023 22:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
framework This is relevant to the framework
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants