diff --git a/src/gallium/drivers/d3d12/d3d12_compiler.cpp b/src/gallium/drivers/d3d12/d3d12_compiler.cpp index 26e4be771d8..e07f53729a7 100644 --- a/src/gallium/drivers/d3d12/d3d12_compiler.cpp +++ b/src/gallium/drivers/d3d12/d3d12_compiler.cpp @@ -354,7 +354,7 @@ fill_mode_lowered(struct d3d12_context *ctx, const struct pipe_draw_info *dinfo) struct d3d12_shader_selector *vs = ctx->gfx_stages[PIPE_SHADER_VERTEX]; if ((ctx->gfx_stages[PIPE_SHADER_GEOMETRY] != NULL && - !ctx->gfx_stages[PIPE_SHADER_GEOMETRY]->is_gs_variant) || + !ctx->gfx_stages[PIPE_SHADER_GEOMETRY]->is_variant) || ctx->gfx_pipeline_state.rast == NULL || (dinfo->mode != PIPE_PRIM_TRIANGLES && dinfo->mode != PIPE_PRIM_TRIANGLE_STRIP)) @@ -394,7 +394,7 @@ needs_point_sprite_lowering(struct d3d12_context *ctx, const struct pipe_draw_in struct d3d12_shader_selector *vs = ctx->gfx_stages[PIPE_SHADER_VERTEX]; struct d3d12_shader_selector *gs = ctx->gfx_stages[PIPE_SHADER_GEOMETRY]; - if (gs != NULL && !gs->is_gs_variant) { + if (gs != NULL && !gs->is_variant) { /* There is an user GS; Check if it outputs points with PSIZE */ return (gs->initial->info.gs.output_primitive == GL_POINTS && (gs->initial->info.outputs_written & VARYING_BIT_PSIZ || @@ -417,7 +417,7 @@ static unsigned cull_mode_lowered(struct d3d12_context *ctx, unsigned fill_mode) { if ((ctx->gfx_stages[PIPE_SHADER_GEOMETRY] != NULL && - !ctx->gfx_stages[PIPE_SHADER_GEOMETRY]->is_gs_variant) || + !ctx->gfx_stages[PIPE_SHADER_GEOMETRY]->is_variant) || ctx->gfx_pipeline_state.rast == NULL || ctx->gfx_pipeline_state.rast->base.cull_face == PIPE_FACE_NONE) return PIPE_FACE_NONE; @@ -435,7 +435,7 @@ get_provoking_vertex(struct d3d12_selection_context *sel_ctx, bool *alternate, c struct d3d12_shader_selector *vs = sel_ctx->ctx->gfx_stages[PIPE_SHADER_VERTEX]; struct d3d12_shader_selector *gs = sel_ctx->ctx->gfx_stages[PIPE_SHADER_GEOMETRY]; - struct d3d12_shader_selector *last_vertex_stage = gs && !gs->is_gs_variant ? gs : vs; + struct d3d12_shader_selector *last_vertex_stage = gs && !gs->is_variant ? gs : vs; /* Make sure GL prims match Gallium prims */ STATIC_ASSERT(GL_POINTS == PIPE_PRIM_POINTS); @@ -457,7 +457,7 @@ get_provoking_vertex(struct d3d12_selection_context *sel_ctx, bool *alternate, c bool flatshade_first = sel_ctx->ctx->gfx_pipeline_state.rast && sel_ctx->ctx->gfx_pipeline_state.rast->base.flatshade_first; *alternate = (mode == GL_TRIANGLE_STRIP || mode == GL_TRIANGLE_STRIP_ADJACENCY) && - (!gs || gs->is_gs_variant || + (!gs || gs->is_variant || gs->initial->info.gs.vertices_out > u_prim_vertex_count(mode)->min); return flatshade_first ? 0 : u_prim_vertex_count(mode)->min - 1; } @@ -580,7 +580,7 @@ validate_geometry_shader_variant(struct d3d12_selection_context *sel_ctx) d3d12_shader_selector *gs = ctx->gfx_stages[PIPE_SHADER_GEOMETRY]; /* Nothing to do if there is a user geometry shader bound */ - if (gs != NULL && !gs->is_gs_variant) + if (gs != NULL && !gs->is_variant) return; /* Fill the geometry shader variant key */ @@ -619,6 +619,37 @@ validate_geometry_shader_variant(struct d3d12_selection_context *sel_ctx) ctx->gfx_stages[PIPE_SHADER_GEOMETRY] = gs; } +static void +validate_tess_ctrl_shader_variant(struct d3d12_selection_context *sel_ctx) +{ + struct d3d12_context *ctx = sel_ctx->ctx; + d3d12_shader_selector *vs = ctx->gfx_stages[PIPE_SHADER_VERTEX]; + d3d12_shader_selector *tcs = ctx->gfx_stages[PIPE_SHADER_TESS_CTRL]; + d3d12_shader_selector *tes = ctx->gfx_stages[PIPE_SHADER_TESS_EVAL]; + struct d3d12_tcs_variant_key key = {0}; + + /* Nothing to do if there is a user tess ctrl shader bound */ + if (tcs != NULL && !tcs->is_variant) + return; + + bool variant_needed = tes != nullptr; + + /* Fill the variant key */ + if (variant_needed) { + fill_varyings(&key.varyings, vs->initial, nir_var_shader_out, + vs->initial->info.outputs_written, false); + key.vertices_out = ctx->patch_vertices; + } + + /* Check if the currently bound tessellation control shader variant is correct */ + if (tcs && memcmp(&tcs->tcs_key, &key, sizeof(key)) == 0) + return; + + /* Find/create the proper variant and bind it */ + tcs = variant_needed ? d3d12_get_tcs_variant(ctx, &key) : NULL; + ctx->gfx_stages[PIPE_SHADER_TESS_CTRL] = tcs; +} + static bool d3d12_compare_shader_keys(const d3d12_shader_key *expect, const d3d12_shader_key *have) { @@ -779,7 +810,7 @@ d3d12_fill_shader_key(struct d3d12_selection_context *sel_ctx, /* We require as outputs what the next stage reads, * except certain system values */ if (next) { - if (!next->is_gs_variant) { + if (!next->is_variant) { if (stage == PIPE_SHADER_VERTEX) system_generated_in_values |= VARYING_BIT_POS; uint64_t mask = next->current->nir->info.inputs_read & ~system_generated_in_values; @@ -819,11 +850,11 @@ d3d12_fill_shader_key(struct d3d12_selection_context *sel_ctx, key->gs.stream_output_factor = 6; } else if (sel_ctx->fill_mode_lowered == PIPE_POLYGON_MODE_LINE) { key->gs.stream_output_factor = 2; - } else if (sel_ctx->needs_vertex_reordering && !sel->is_gs_variant) { + } else if (sel_ctx->needs_vertex_reordering && !sel->is_variant) { key->gs.triangle_strip = 1; } - if (sel->is_gs_variant && next && next->initial->info.inputs_read & VARYING_BIT_PRIMITIVE_ID) + if (sel->is_variant && next && next->initial->info.inputs_read & VARYING_BIT_PRIMITIVE_ID) key->gs.primitive_id = 1; } else if (stage == PIPE_SHADER_FRAGMENT) { key->fs.missing_dual_src_outputs = sel_ctx->missing_dual_src_outputs; @@ -901,7 +932,7 @@ d3d12_fill_shader_key(struct d3d12_selection_context *sel_ctx, if (stage == PIPE_SHADER_FRAGMENT && sel_ctx->ctx->gfx_stages[PIPE_SHADER_GEOMETRY] && - sel_ctx->ctx->gfx_stages[PIPE_SHADER_GEOMETRY]->is_gs_variant && + sel_ctx->ctx->gfx_stages[PIPE_SHADER_GEOMETRY]->is_variant && sel_ctx->ctx->gfx_stages[PIPE_SHADER_GEOMETRY]->gs_key.has_front_face) { key->fs.remap_front_facing = 1; } @@ -1338,6 +1369,7 @@ d3d12_select_shader_variants(struct d3d12_context *ctx, const struct pipe_draw_i sel_ctx.manual_depth_range = manual_depth_range(ctx); validate_geometry_shader_variant(&sel_ctx); + validate_tess_ctrl_shader_variant(&sel_ctx); for (unsigned i = 0; i < ARRAY_SIZE(order); ++i) { auto sel = ctx->gfx_stages[order[i]]; diff --git a/src/gallium/drivers/d3d12/d3d12_compiler.h b/src/gallium/drivers/d3d12/d3d12_compiler.h index 8fd834efe59..8d49e285e72 100644 --- a/src/gallium/drivers/d3d12/d3d12_compiler.h +++ b/src/gallium/drivers/d3d12/d3d12_compiler.h @@ -45,6 +45,8 @@ enum d3d12_state_var { D3D12_STATE_VAR_PT_SPRITE, D3D12_STATE_VAR_DRAW_PARAMS, D3D12_STATE_VAR_DEPTH_TRANSFORM, + D3D12_STATE_VAR_DEFAULT_INNER_TESS_LEVEL, + D3D12_STATE_VAR_DEFAULT_OUTER_TESS_LEVEL, D3D12_MAX_GRAPHICS_STATE_VARS, D3D12_STATE_VAR_NUM_WORKGROUPS = 0, @@ -203,6 +205,12 @@ struct d3d12_gs_variant_key struct d3d12_varying_info varyings; }; +struct d3d12_tcs_variant_key +{ + unsigned vertices_out; + struct d3d12_varying_info varyings; +}; + struct d3d12_shader_selector { enum pipe_shader_type stage; nir_shader *initial; @@ -215,8 +223,11 @@ struct d3d12_shader_selector { unsigned compare_with_lod_bias_grad:1; unsigned workgroup_size_variable:1; - bool is_gs_variant; - struct d3d12_gs_variant_key gs_key; + bool is_variant; + union { + struct d3d12_gs_variant_key gs_key; + struct d3d12_tcs_variant_key tcs_key; + }; }; struct d3d12_context; @@ -250,6 +261,15 @@ d3d12_gs_variant_cache_destroy(struct d3d12_context *ctx); struct d3d12_shader_selector * d3d12_get_gs_variant(struct d3d12_context *ctx, struct d3d12_gs_variant_key *key); +void +d3d12_tcs_variant_cache_init(struct d3d12_context *ctx); + +void +d3d12_tcs_variant_cache_destroy(struct d3d12_context *ctx); + +struct d3d12_shader_selector * +d3d12_get_tcs_variant(struct d3d12_context *ctx, struct d3d12_tcs_variant_key *key); + #ifdef __cplusplus } #endif diff --git a/src/gallium/drivers/d3d12/d3d12_context.cpp b/src/gallium/drivers/d3d12/d3d12_context.cpp index 42656f5597b..20e7c629c31 100644 --- a/src/gallium/drivers/d3d12/d3d12_context.cpp +++ b/src/gallium/drivers/d3d12/d3d12_context.cpp @@ -2272,6 +2272,16 @@ d3d12_set_patch_vertices(struct pipe_context *pctx, uint8_t patch_vertices) ctx->cmdlist_dirty |= D3D12_DIRTY_PRIM_MODE; } +static void +d3d12_set_tess_state(struct pipe_context *pctx, + const float default_outer_level[4], + const float default_inner_level[2]) +{ + struct d3d12_context *ctx = d3d12_context(pctx); + memcpy(ctx->default_outer_tess_factor, default_outer_level, sizeof(ctx->default_outer_tess_factor)); + memcpy(ctx->default_inner_tess_factor, default_inner_level, sizeof(ctx->default_inner_tess_factor)); +} + struct pipe_context * d3d12_context_create(struct pipe_screen *pscreen, void *priv, unsigned flags) { @@ -2331,6 +2341,7 @@ d3d12_context_create(struct pipe_screen *pscreen, void *priv, unsigned flags) ctx->base.delete_tes_state = d3d12_delete_tes_state; ctx->base.set_patch_vertices = d3d12_set_patch_vertices; + ctx->base.set_tess_state = d3d12_set_tess_state; ctx->base.create_compute_state = d3d12_create_compute_state; ctx->base.bind_compute_state = d3d12_bind_compute_state; @@ -2404,6 +2415,7 @@ d3d12_context_create(struct pipe_screen *pscreen, void *priv, unsigned flags) d3d12_root_signature_cache_init(ctx); d3d12_cmd_signature_cache_init(ctx); d3d12_gs_variant_cache_init(ctx); + d3d12_tcs_variant_cache_init(ctx); d3d12_compute_transform_cache_init(ctx); util_dl_library *d3d12_mod = util_dl_open(UTIL_DL_PREFIX "d3d12" UTIL_DL_EXT); diff --git a/src/gallium/drivers/d3d12/d3d12_context.h b/src/gallium/drivers/d3d12/d3d12_context.h index 52c890e26b8..6c480e94273 100644 --- a/src/gallium/drivers/d3d12/d3d12_context.h +++ b/src/gallium/drivers/d3d12/d3d12_context.h @@ -173,6 +173,7 @@ struct d3d12_context { struct hash_table *root_signature_cache; struct hash_table *cmd_signature_cache; struct hash_table *gs_variant_cache; + struct hash_table *tcs_variant_cache; struct hash_table *compute_transform_cache; struct d3d12_batch batches[4]; @@ -222,6 +223,8 @@ struct d3d12_context { D3D12_STREAM_OUTPUT_BUFFER_VIEW fake_so_buffer_views[PIPE_MAX_SO_BUFFERS]; unsigned fake_so_buffer_factor; uint8_t patch_vertices; + float default_outer_tess_factor[4]; + float default_inner_tess_factor[2]; struct d3d12_shader_selector *gfx_stages[D3D12_GFX_SHADER_STAGES]; struct d3d12_shader_selector *compute_state; diff --git a/src/gallium/drivers/d3d12/d3d12_draw.cpp b/src/gallium/drivers/d3d12/d3d12_draw.cpp index 4bc2b1bf5e3..a98973a76ba 100644 --- a/src/gallium/drivers/d3d12/d3d12_draw.cpp +++ b/src/gallium/drivers/d3d12/d3d12_draw.cpp @@ -380,6 +380,14 @@ fill_graphics_state_vars(struct d3d12_context *ctx, ptr[1] = fui(ctx->viewport_states[0].translate[2] - ctx->viewport_states[0].scale[2]); size += 4; break; + case D3D12_STATE_VAR_DEFAULT_INNER_TESS_LEVEL: + memcpy(ptr, ctx->default_inner_tess_factor, sizeof(ctx->default_inner_tess_factor)); + size += 4; + break; + case D3D12_STATE_VAR_DEFAULT_OUTER_TESS_LEVEL: + memcpy(ptr, ctx->default_outer_tess_factor, sizeof(ctx->default_outer_tess_factor)); + size += 4; + break; default: unreachable("unknown state variable"); } @@ -706,7 +714,7 @@ static inline struct d3d12_shader_selector * d3d12_last_vertex_stage(struct d3d12_context *ctx) { struct d3d12_shader_selector *sel = ctx->gfx_stages[PIPE_SHADER_GEOMETRY]; - if (!sel || sel->is_gs_variant) + if (!sel || sel->is_variant) sel = ctx->gfx_stages[PIPE_SHADER_TESS_EVAL]; if (!sel) sel = ctx->gfx_stages[PIPE_SHADER_VERTEX]; diff --git a/src/gallium/drivers/d3d12/d3d12_gs_variant.cpp b/src/gallium/drivers/d3d12/d3d12_gs_variant.cpp index 4801285dbe9..f3bb2f93edc 100644 --- a/src/gallium/drivers/d3d12/d3d12_gs_variant.cpp +++ b/src/gallium/drivers/d3d12/d3d12_gs_variant.cpp @@ -466,7 +466,7 @@ create_geometry_shader_variant(struct d3d12_context *ctx, struct d3d12_gs_varian gs = d3d12_emit_lines(ctx, key); if (gs) { - gs->is_gs_variant = true; + gs->is_variant = true; gs->gs_key = *key; } diff --git a/src/gallium/drivers/d3d12/d3d12_nir_passes.h b/src/gallium/drivers/d3d12/d3d12_nir_passes.h index 80aeb8db068..8d204841f43 100644 --- a/src/gallium/drivers/d3d12/d3d12_nir_passes.h +++ b/src/gallium/drivers/d3d12/d3d12_nir_passes.h @@ -35,6 +35,13 @@ struct d3d12_shader; struct d3d12_image_format_conversion_info; enum d3d12_state_var; +nir_ssa_def * +d3d12_get_state_var(nir_builder *b, + enum d3d12_state_var var_enum, + const char *var_name, + const struct glsl_type *var_type, + nir_variable **out_var); + nir_ssa_def * d3d12_get_state_var(nir_builder *b, enum d3d12_state_var var_enum, diff --git a/src/gallium/drivers/d3d12/d3d12_tcs_variant.cpp b/src/gallium/drivers/d3d12/d3d12_tcs_variant.cpp new file mode 100644 index 00000000000..2f2bd903b66 --- /dev/null +++ b/src/gallium/drivers/d3d12/d3d12_tcs_variant.cpp @@ -0,0 +1,149 @@ +/* + * Copyright © Microsoft Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "nir.h" +#include "nir_builder.h" +#include "d3d12_context.h" +#include "d3d12_compiler.h" +#include "d3d12_nir_passes.h" +#include "nir_to_dxil.h" + +static uint32_t +hash_tcs_variant_key(const void *key) +{ + return _mesa_hash_data(key, sizeof(struct d3d12_tcs_variant_key)); +} + +static bool +equals_tcs_variant_key(const void *a, const void *b) +{ + return memcmp(a, b, sizeof(struct d3d12_tcs_variant_key)) == 0; +} + +void +d3d12_tcs_variant_cache_init(struct d3d12_context *ctx) +{ + ctx->tcs_variant_cache = _mesa_hash_table_create(NULL, NULL, equals_tcs_variant_key); +} + +static void +delete_entry(struct hash_entry *entry) +{ + d3d12_shader_free((d3d12_shader_selector *)entry->data); +} + +void +d3d12_tcs_variant_cache_destroy(struct d3d12_context *ctx) +{ + _mesa_hash_table_destroy(ctx->tcs_variant_cache, delete_entry); +} + +static struct d3d12_shader_selector * +create_tess_ctrl_shader_variant(struct d3d12_context *ctx, struct d3d12_tcs_variant_key *key) +{ + nir_builder b = nir_builder_init_simple_shader(MESA_SHADER_TESS_CTRL, dxil_get_nir_compiler_options(), "passthrough"); + nir_shader *nir = b.shader; + + nir_ssa_def *invocation_id = nir_load_invocation_id(&b); + uint64_t varying_mask = key->varyings.mask; + + while(varying_mask) { + int var_idx = u_bit_scan64(&varying_mask); + auto var = &key->varyings.vars[var_idx]; + const struct glsl_type *type = var->type; + const struct glsl_type *in_type = var->type; + const struct glsl_type *out_type = var->type; + in_type = glsl_array_type(type, key->vertices_out, 0); + out_type = glsl_array_type(type, key->vertices_out, 0); + + char buf[1024]; + snprintf(buf, sizeof(buf), "in_%d", var->driver_location); + nir_variable *in = nir_variable_create(nir, nir_var_shader_in, in_type, buf); + snprintf(buf, sizeof(buf), "out_%d", var->driver_location); + nir_variable *out = nir_variable_create(nir, nir_var_shader_out, out_type, buf); + out->data.location = in->data.location = var_idx; + out->data.driver_location = in->data.driver_location = var->driver_location; + + for (unsigned i = 0; i < key->vertices_out; i++) { + nir_if *start_block = nir_push_if(&b, nir_ieq(&b, invocation_id, nir_imm_int(&b, i))); + nir_deref_instr *in_array_var = nir_build_deref_array(&b, nir_build_deref_var(&b, in), invocation_id); + nir_ssa_def *load = nir_load_deref(&b, in_array_var); + nir_deref_instr *out_array_var = nir_build_deref_array_imm(&b, nir_build_deref_var(&b, out), i); + nir_store_deref(&b, out_array_var, load, 0xff); + nir_pop_if(&b, start_block); + } + } + nir_variable *gl_TessLevelInner = nir_variable_create(nir, nir_var_shader_out, glsl_array_type(glsl_float_type(), 2, 0), "gl_TessLevelInner"); + gl_TessLevelInner->data.location = VARYING_SLOT_TESS_LEVEL_INNER; + gl_TessLevelInner->data.patch = 1; + gl_TessLevelInner->data.compact = 1; + nir_variable *gl_TessLevelOuter = nir_variable_create(nir, nir_var_shader_out, glsl_array_type(glsl_float_type(), 4, 0), "gl_TessLevelOuter"); + gl_TessLevelOuter->data.location = VARYING_SLOT_TESS_LEVEL_OUTER; + gl_TessLevelOuter->data.patch = 1; + gl_TessLevelOuter->data.compact = 1; + + nir_variable *state_var_inner = NULL, *state_var_outer = NULL; + nir_ssa_def *load_inner = d3d12_get_state_var(&b, D3D12_STATE_VAR_DEFAULT_INNER_TESS_LEVEL, "d3d12_TessLevelInner", glsl_vec_type(2), &state_var_inner); + nir_ssa_def *load_outer = d3d12_get_state_var(&b, D3D12_STATE_VAR_DEFAULT_OUTER_TESS_LEVEL, "d3d12_TessLevelOuter", glsl_vec4_type(), &state_var_outer); + + for (unsigned i = 0; i < 2; i++) { + nir_deref_instr *store_idx = nir_build_deref_array_imm(&b, nir_build_deref_var(&b, gl_TessLevelInner), i); + nir_store_deref(&b, store_idx, nir_channel(&b, load_inner, i), 0xff); + } + for (unsigned i = 0; i < 4; i++) { + nir_deref_instr *store_idx = nir_build_deref_array_imm(&b, nir_build_deref_var(&b, gl_TessLevelOuter), i); + nir_store_deref(&b, store_idx, nir_channel(&b, load_outer, i), 0xff); + } + + nir->info.tess.tcs_vertices_out = key->vertices_out; + nir_validate_shader(nir, "created"); + + struct pipe_shader_state templ; + + templ.type = PIPE_SHADER_IR_NIR; + templ.ir.nir = nir; + templ.stream_output.num_outputs = 0; + + d3d12_shader_selector *tcs = d3d12_create_shader(ctx, PIPE_SHADER_TESS_CTRL, &templ); + if (tcs) { + tcs->is_variant = true; + memcpy(&tcs->tcs_key, key, sizeof(*key)); + } + return tcs; +} + +d3d12_shader_selector * +d3d12_get_tcs_variant(struct d3d12_context *ctx, struct d3d12_tcs_variant_key *key) +{ + uint32_t hash = hash_tcs_variant_key(key); + struct hash_entry *entry = _mesa_hash_table_search_pre_hashed(ctx->tcs_variant_cache, + hash, key); + if (!entry) { + d3d12_shader_selector *tcs = create_tess_ctrl_shader_variant(ctx, key); + entry = _mesa_hash_table_insert_pre_hashed(ctx->tcs_variant_cache, + hash, &tcs->tcs_key, tcs); + assert(entry); + } + + return (d3d12_shader_selector *)entry->data; +} diff --git a/src/gallium/drivers/d3d12/meson.build b/src/gallium/drivers/d3d12/meson.build index 591b67955d4..97ea47fb60b 100644 --- a/src/gallium/drivers/d3d12/meson.build +++ b/src/gallium/drivers/d3d12/meson.build @@ -45,6 +45,7 @@ files_libd3d12 = files( 'd3d12_root_signature.cpp', 'd3d12_screen.cpp', 'd3d12_surface.cpp', + 'd3d12_tcs_variant.cpp', ) if host_machine.system() == 'windows'